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.