Sahan Serasinghe

Senior Software Engineer | Master of Data Science

ASP.NET Core Health Checks

2021-03-25asp.net core 6 min read

When you are developing a project where you have multiple services talking to each other, it would be hard to see their service health instantly.

This article will look at how we can configure ASP.NET Core health checks and look into what kind of different metrics we can gather from it.

💡 Follow along with the code samples for this blog post from this repo.

The setup

I will be using ASP.NET’s MVC and template, as most of you are familiar with it. However, you can also use other project types such as API, Console or even Blazor.

Let’s scaffold an MVC app.

dotnet new mvc -n Monitor
dotnet new sln
dotnet sln add Monitor

First, we need to install the HealthChecks package.

cd Monitor/
dotnet add package AspNetCore.HealthChecks.UI

Now we are ready to hook it up to the middleware pipeline.

Application Configuration

Navigate to the Startup.cs and, let’s add the following in the ConfigureServices method.

services.AddHealthChecks();

For starters, we will add an endpoint to spit out a JSON to show the app’s current status.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

		// Mapping a new endpoint to see the health
    endpoints.MapHealthChecks("/health");
});

aspdotnet-core-health-checks-1.png

Well, that isn’t particularly useful. This health checks package we are using provides many different output formats. They have made it extensible so that you can even use a custom output format. If you have many services talking to each other, it will make sense to use a format like JSON.

💡 Feel free to skip this section and jump ahead to “Registering health checks” section to see the final result.

Health checks UI dashboard

We are interested in seeing is a Dashboard that comes out-of-the-box. We will use our current project as a monitoring app to probe in and check the health status of some other applications that are running. The following diagram explains our desired state.

aspdotnet-core-health-checks-2.jpg

Let’s get to a point we can see something visually and start extending our solution on top of that. To be able to use the dashboard, it needs to have a backing store. We need to bring in a package called AspNetCore.HealthChecks.UI.InMemory.Storage

dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage

Let’s change the code in our Startup/ConfigureServices class. Replace the previous code with the following.

// ..
services.AddHealthChecksUI()
        .AddInMemoryStorage();
// ..

Next, let’s map a new endpoint to be able to see the dashboard.

// ...
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    endpoints.MapHealthChecksUI();
});

//...

Now we are ready to run the application and see what we get.

dotnet new webapi -n Service1
dotnet new webapi -n Service2
dotnet sln add Service1 Service2

We need to add the health checks middleware to the two projects we created in the above step.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
		// ...
    services.AddHealthChecks();
		// ...
}

app.UseEndpoints(endpoints =>
{
    // ...
    endpoints.MapHealthChecks("/health", new HealthCheckOptions()
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
		// ...
});

Just copy and add the following lines to the .csproj files of the two services we created.

<PackageReference Include="AspNetCore.HealthChecks.UI" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />

Finally, you need to add the endpoints of the services to our Monitor project. Let’s add the following bit to the appsettings.json file of the Monitor project. Head over to the official docs to learn more about the configuration.

"HealthChecksUI": {
  "HealthChecks": [
    {
      "Name": "Service 1",
      "Uri": "https://localhost:5011/health"
    },
    {
      "Name": "Service 2",
      "Uri": "https://localhost:5021/health"
    }
  ],
  "EvaluationTimeInSeconds": 10
},

💡 Don’t forget to take the port numbers your servers are running on from the corresponding launchSettings.json files. This could change depending on whether you are using IIS Express or Kestrel for development.

If you fire up the 3 projects now, you will be able to see the health checks dashboard.

The default URI for the dashboard UI is https://localhost:<app port>/healthchecks-ui

aspdotnet-core-health-checks-3.png

Under the hood, this small SPA dashboard polls the /healthchecks-api URI which returns a JSON.

Registering health checks

Without bringing in any other dependencies let’s simulate the 3 states that it will report to us:

Head over here to see a complete list of supported health checks. Let’s add some dummy health checks to see them in action in the dashboard.

We will replace the code in Service1/Startup.cs with the following.

services.AddHealthChecks()
    .AddCheck("Foo", () =>
        HealthCheckResult.Healthy("Foo is OK!"), tags: new[] { "foo_tag" })
    .AddCheck("Bar", () =>
        HealthCheckResult.Unhealthy("Bar is unhealthy!"), tags: new[] { "bar_tag" })
    .AddCheck("Baz", () =>
        HealthCheckResult.Degraded("Baz is degraded!"), tags: new[] { "baz_tag" });

aspdotnet-core-health-checks-4.png

In my previous blog post, I showed you how we could easily integrate with the Redis. I will be using AspNetCore.HealthChecks.Redis package to configure the health checks.

dotnet add package AspNetCore.HealthChecks.Redis

We will also add this configuration bit to its appsettings.json file.

"Redis": {
  "ConnectionString":  "localhost:5002" 
},

Let’s spin up a Redis docker container on port 5002.

docker run --name redis-cache -p 5002:6379 -d redis

Dashboard:

aspdotnet-core-health-checks-5.png

Feel free to stop the docker container and see how the errors get displayed in the dashboard.

aspdotnet-core-health-checks-6.png

Adding custom health checks

Now let’s switch to our Service2 project and do something interesting with it as well. We are going to be adding our custom health check.

public class RemoteHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
    {
        var isHealthy = CheckRemoteEndpointHealth();

        return Task.FromResult(isHealthy ? 
            HealthCheckResult.Healthy("Remote endpoint is healthy.") :
            HealthCheckResult.Unhealthy("Remote endpoint is unhealthy"));
    }

    private bool CheckRemoteEndpointHealth()
    {
        // Just stubbing it out for demo
        var rnd = new Random().Next(1, 5);
        return rnd % 2 != 0;
    }
}

We can now register RemoteHealthCheck in the Startup class like so.

services.AddHealthChecks()
        .AddCheck<RemoteHealthCheck>(nameof(RemoteHealthCheck));

Now the dashboard will show you the status of both services.

aspdotnet-core-health-check-7.png

You’ll see that the RemoteHealthCheck will be going down from time to time because we have set it to return an unhealthy randomly. You can also set the EvaluationTimeInSeconds setting to like 2s to see the result quickly.

aspdotnet-core-health-checks-8.png

Conclusion

Today we looked at how you can improve your developer experience by leveraging ASP.NET’s health checks. There are so many providers and configurations to play around with. Feel free to head over to the docs to learn more.

References

  1. https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks
  2. https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-5.0
Loading...
Sahan Serasinghe - Engineering Blog

Sahan Serasinghe Senior Software Engineer at Canva | Azure Solutions Architect Expert | Master of Data Science at UIUC | CKAD