Health Checks in ASP.NET Core

Learn how to configure and develop health checks in ASP.NET Core to confirm the health of your application.

Health checks are a new middleware available in ASP.NET Core 2.2. It provides a way to expose the health of your application through an HTTP endpoint.

The health of your application can mean many things. It's up to you to configure what is considered healthy or unhealthy.

Maybe your application is reliant on the ability to connect to a database. If your application cannot connect to the database, then the health check endpoint would respond as unhealthy.

Other scenarios could include confirming the environment that is hosting the application is in a healthy state. For example, memory usage, disk space, etc.
If you have used a load balancer you've probably used at least a basic health check. Likewise, if you've used docker, you may be familiar with its HEALTHCHECK.

In a load balancing scenario, this means that the load balancer periodically makes an HTTP request to your health check endpoint. If it receives a HTTP 200 OK status, then it adds the application to the load balancer pool and live HTTP traffic will be routed to that instance.

If it responds with an unhealthy status (usually anything other than HTTP 200 OK), it will not add or remove it from the load balancer pool.

Basics

The bare minimum to get health checks added to your application are to modify the Startup.cs file by adding health checks to the ConfigureServices and Configure methods appropriately.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCore.HealthCheck.Demo
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddHealthChecks();
      services.AddDbContext<MyDbContext>();
      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
      app.UseHealthChecks("/health");
      app.UseStaticFiles();
      app.UseMvc();
    }
  }
}

When you browse to the /health route, you will receive an HTTP 200 OK with the content body of Healthy.

Custom Health Check

One common health check might be to verify that we can connect to our database. In this example, I have an Entity Framework Core DbContext called MyDbContext, which is registered in ConfigureServices().

In order to test our database connection, we can create a custom health check. To do so, we need to implement IHealthCheck. The CheckhealthAsync requires us to return a HealthCheckStatus. If we are able to connect to the database, we will return Healthy; otherwise, we will return Unhealthy.

You will also notice that we are using dependency injection through the constructor. Anything registered in ConfigureServices() is available for us to inject in the constructor of our health check.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace AspNetCore.HealthCheck.Demo
{
  public class DbContextHealthCheck : IHealthCheck
  {
    private readonly MyDbContext _dbContext;


    public DbContextHealthCheck(MyDbContext dbContext)
    {
      _dbContext = dbContext;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
      CancellationToken cancellationToken = new CancellationToken())
    {
      return await _dbContext.Database.CanConnectAsync(cancellationToken)
              ? HealthCheckResult.Healthy()
              : HealthCheckResult.Unhealthy();
    }
  }
}

Now, in order to use add new health check, we can use Addcheck() to AddHealthChecks() in ConfigureServices():

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCore.HealthCheck.Demo
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddDbContext<MyDbContext>();

      services.AddHealthChecks()
              .AddCheck<MyDbContextHealthCheck>("DbContextHealthCheck");

      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
      app.UseHealthChecks("/health");           
      app.UseStaticFiles();
      app.UseMvc();
    }
  }
}

Built-in EF Core Check

Luckily, we don't actually need to create an EF Core DbContext check as Microsoft has already done so in the Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore NuGet Package. We can simply use this package and then change our check to the following:

services.AddHealthChecks()
        .AddDbContextCheck<MyDbContext>("DbContextHealthCheck");

Community Packages

There are a bunch of health check packages on NuGet for SQL Server, MySQL, MongoDB, Redis, RabbitMQ, Elasticsearch, Azure Storage, Amazon S3, and many more.

You can find all of these on AspNetCore.Diagnostics.HealthChecks repository on GitHub that reference each NuGet package.

Here's a couple examples of how easily they are to add to your Startup's ConfigureServices():

AspNetCore.HealthChecks.SqlServer

public void ConfigureServices(IServiceCollection services)
{
  services.AddHealthChecks()
          .AddSqlServer(Configuration["Data:ConnectionStrings:Sql"])
}

AspNetCore.HealthChecks.Redis

public void ConfigureServices(IServiceCollection services)
{
  services.AddHealthChecks()
          .AddRedis(Configuration["Data:ConnectionStrings:Redis"])
}

Options

There are a few different options for configuring how the health check middleware behaves.

Status Codes

In our own DbContextHealthCheck, we returned a Healthy status if our application can connect to our database. Otherwise, we returned an Unhealthy status.

This results in the /health endpoint returning different HTTP status codes depending on our HealthStatus. By default, healthy will return a HTTP Status of 200 OK. Unhealthy will return a 503 Service Unavailable. We can modify this default behavior by using HealthCheckOptions to create our mappings between HealthStatus and StatusCodes.

app.UseHealthChecks("/health", new HealthCheckOptions
{
  ResultStatusCodes =
  {
    [HealthStatus.Healthy] = StatusCodes.Status200OK,
    [HealthStatus.Degraded] = StatusCodes.Status200OK,
    [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,
  }
});

There is a third status code: Degraded. By default, this will also return a 200 OK status.

Response

By default, the Content-Type of the response will be text/plain and the response body will be Healthy or Unhealthy.

Another option you may want to configure is the actual response body of the endpoint. You can control the output by configuring the ResponseWriter in the HealthCheckOptions.

Instead of returning plain text, I'll serialize the HealthReport to JSON:

app.UseHealthChecks("/health", new HealthCheckOptions
{
  ResponseWriter = async (context, report) =>
  {
    context.Response.ContentType = "application/json; charset=utf-8";
    var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
    await context.Response.Body.WriteAsync(bytes);
  }
});

This results in our /health endpoint returning:

Content-Type: application/json; charset=utf-8
Server: Kestrel
Cache-Control: no-store, no-cache
Pragma:no-cache
Transfer-Encoding: chunked
Expires: Thu, 01 Jan 1970 00:00:00 GMT
{
  "Entries": {
    "DbContextHealthCheck": {
      "Data": {},
      "Description": null,
      "Duration": "00:00:00.0265244",
      "Exception": null,
      "Status": 2
    }
  },
  "Status": 2,
  "TotalDuration": "00:00:00.0302606"
}

Timeouts

One thing to consider when creating health checks is timeouts. For example, maybe you've created a health check is testing a database connection or perhaps you are using HttpClient to verify you can make an external HTTP connection.

Often, these clients (DbConnection or HttpClient) have default timeout lengths that can be fairly high. HttpClient has a default of 100 seconds.

If you are using the health check endpoint for a load balancer to determine the health of your application, you want to have it return its health status as quickly as possible. If you have a internet connection issue, you may not want to wait 100 seconds to return a 503 Service Unavailable. This will delay your load balancer from removing your application from the pool.

Web UI

There is another excellent package, AspNetCore.HealthChecks.UI that adds a web UI to your app. This allows you to visualize the health checks you have configured and their status.

Once the package is installed, you need to call AddHealthChecksUI() to ConfigureServices() as well as call UseHealthChecksUI() from Configure() in your Startup.

You also need to configure the ResponseWrite to use the UIResponseWriter.WriteHealthCheckUIResponse. This essentially does what we have above by serializing the HealthReport to JSON. This is required by the HealthCheck-UI in order for it to get detailed information about your configured health checks.

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
  }

  public IConfiguration Configuration { get; }

  public void ConfigureServices(IServiceCollection services)
  {
    services.AddDbContext<MyDbContext>();


    services.AddHealthChecks()
            .AddCheck<MyDbContextHealthCheck>("DbContextHealthCheck");
    
    services.AddHealthChecksUI();
    
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  }

  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    app.UseHealthChecks("/health", new HealthCheckOptions()
    {
      Predicate = _ => true,
      ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
    
    app.UseHealthChecksUI();
    
    app.UseStaticFiles();
    app.UseMvc();
  }
}

This will add a new route to your application at /healthchecks-ui.

You also need to add some configuration to appsettings.json (or whatever configuration you are pulling from).

This tells the HealthChecks-UI where to poll for the detailed health check information. Because of this, you could add as many different URLs for various other ASP.NET Core applications that are returning health check data.

For our example, we will just add our local endpoint we have running at /health:

"HealthChecks-UI": {
  "HealthChecks": [
    {
      "Name": "Local",
      "Uri": "http://localhost:5000/health"
    }
  ],
  "EvaluationTimeOnSeconds": 10,
  "MinimumSecondsBetweenFailureNotifications": 60
}

Now when you browse to /healtchecks-ui, you will see the UI with a listing of our health checks:

Health Check UI

Summary

The health check middleware is a great new addition to ASP.NET Core. It is configurable and very easy to add your own new health checks. The community is already releasing many different packages for various external services, and I can only assume they will increase in the future.

For More on Developing with ASP.NET Core

Want to learn about creating great user interfaces with ASP.NET Core? Check out Telerik UI for ASP.NET Core, with everything from grids and charts to schedulers and pickers.


DerekComartin
About the Author

Derek Comartin

Derek Comartin is software developer and Microsoft MVP with two decades of professional experience that span enterprise, professional services and product development. He’s written software for a variety of business domains, such as consumer goods, distribution, transportation, manufacturing, and accounting. He founded and leads the Windsor-Essex .NET Developers Group (@WENetDevelopers). Derek has a very active blog, codeopinion.com, that focuses on .NET, CQRS, Event Sourcing, HTTP APIs and Hypermedia.

Related Posts

Comments

Comments are disabled in preview mode.