I’ve recently been reading up on .NET Aspire and have found a lot of cool .NET tech underpinning the offering. One of
the tools that has gotten a glow-up since I last looked at it has been Microsoft.Extensions.Diagnostics.HealthChecks
package provides you with the infrastructure to perform various types of system health monitoring.
In this post, we’ll look at installing the health checks package into existing ASP.NET Core applications and using an additional package to perform health checks on your databases using Entity Framework Core.
Install and Set Up Health Checks
In an existing ASP.NET Core application, you’ll need to install the following package.
dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks
Once the package has been installed, you must do several setup tasks in your Program
file.
The first step is to register the HealthChecks
middleware and services in the services collection.
builder.Services.AddHealthChecks();
Next, in your ASP.NET Core request pipeline, you’ll need to register the middleware. You can choose whether to place the middleware before or after the authorization middleware, but as you’ll see, there might not be much reason to have this behind an auth check.
You’ll need to choose a URL path to access your health check. I used the /health
path for this case, but feel free to
choose whatever suits you.
app.UseRouting();
app.UseHealthChecks("/health");
app.UseAuthorization();
Navigating to the /health
endpoint will get you the following response.
Healthy
The response will always be a string of Healthy
, Unhealthy
, or Degraded
. This makes it simple for third-party
health check systems to determine your application’s general health quickly. Your logging system will handle the details
of the issues.
Let’s write a new health check! You’ll need to implement the IHealthCheck
interface and implement the
CheckHealthAsync
method.
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Healthnut.Services;
public class KhalidHealthCheck: IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
{
return Task.FromResult(
HealthCheckResult.Degraded("Khalid ate some ice cream")
);
}
}
These classes are added to your services collection, so they can opt-in to any dependency injection mechanisms you might
want to use. Let’s add it to our health checks. Modify the registration in the Program
file.
builder.Services
.AddHealthChecks()
.AddCheck<KhalidHealthCheck>("Khalid");
Note that health checks require a unique name. You’ll see a Degraded
string after rerunning the app and hitting the
/health
endpoint. What’s more interesting is that you’ll see a new log message on your terminal.
warn: Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService[103]
Health check Khalid with status Degraded completed after 2.5366ms with message 'Khalid ate some ice cream'
Health checks are async
, so you can write any logic you need to determine the health of your system, but be sure it’s
snappy and non-resource-intensive checks; otherwise, ironically, the health checks could lead to unhealthy results.
If you hate the formalities of classes and interfaces, you can also choose to implement health checks directly at the point of registration. This can be helpful for microservices or smaller utility applications.
builder.Services
.AddHealthChecks()
.AddCheck<KhalidHealthCheck>("Khalid")
.AddAsyncCheck("Butter", async () =>
{
await Task.Delay(1000);
return new HealthCheckResult(HealthStatus.Healthy, "Butter is good");
});
So far, it’s been good, but let’s now use health checks for our database.
Health checks for Entity Framework Core
You’ll need to install a new package to get a new extension method for EF Core health checks. You should already have an
Entity Framework Core application with a DbContext
implementation. If you don’t, create one. Now, the package.
dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
Once installed, we can update our health check registration using the following line.
builder.Services
.AddHealthChecks()
.AddCheck<KhalidHealthCheck>("Khalid")
.AddAsyncCheck("Butter", async () =>
{
await Task.Delay(1000);
return new HealthCheckResult(HealthStatus.Healthy, "Butter is good");
})
.AddDbContextCheck<Database>(
"people check",
customTestQuery: (db, token) => db.People.AnyAsync(token)
);
The AddDbContextCheck
takes a generic argument of a DbContext
implementation, and the customTestQuery
argument is
a query you can execute to verify the health of your database. The provided query must return a single bool
value. Use
logical LINQ operators such as Any
and All
, or write queries that evaluate entirely on the server with limited
results returned. Oh, and keep these queries snappy and to the point. Doing expensive database checks may impact the
health of your application.
Rerunning the application, we’ll see the log message we expect.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (14ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT EXISTS (
SELECT 1
FROM "People" AS "p")
fail: Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService[103]
Health check people check with status Unhealthy completed after 1545.2538ms with message '(null)'
Neat!
Conclusion
We barely scratched the surface of what’s possible with the health checks feature of ASP.NET Core, and there’s so much more you can do to provide real-time health updates of your applications. There’s also a wide variety of health check extensions on NuGet, so you’ll rarely have to write your own, but it’s nice knowing you could with such a straightforward interface.
Finally, It’s important to remember that you’ll need a monitoring tool to watch these endpoints to get the most out of them, but I’ll leave that decision up to you.
As always, thanks for reading and cheers.