Is your service healthy? Healthchecks in ASP.NET Core 6.0

Krzysztof Waśko

Introduction

In the world of microservices and distributed systems, it’s crucial to monitor the health of each service to maintain the system’s overall stability and performance. ASP.NET Core 6.0 has built-in support for health checks, allowing developers to easily implement health checks in their applications. In this article, we will explore how to implement health checks in ASP.NET Core 6.0 using both built-in features and third-party libraries like AspNetCore.Diagnostics.HealthChecks.

Prerequisites

To follow along with the examples in this article, you should have:

  1. Basic knowledge about .NET 6.0.
  2. A text editor for writing code (e.g. Visual Studio Code).

Getting Started

To begin, we will need to create a new ASP.NET Core 6.0 Web API project. You can do this using the following command:

dotnet new webapi -n HealthCheckExample -f net6.0

Once the project is created, navigate to the HealthCheckExample folder and open the Program.cs file. We will start by adding the health check middleware to the application.

Adding Health Check Middleware

To add the health check middleware, we need to register the AddHealthChecks service in the ConfigureServices method of our Program.cs file. Update the Main method to include the following code:

using var builder = WebApplication.CreateBuilder(args);

// Add Health Checks
builder.Services.AddHealthChecks();

var app = builder.Build();

// Add Health Check endpoint
app.MapHealthChecks("/health");

app.MapControllers();

app.Run();

In the code above, we added the health check service by calling AddHealthChecks() on the Services property of the builder. Next, we created an endpoint for the health check at the path "/health" using the MapHealthChecks method.

Testing Basic Health Check

Now that we have the health check middleware and an endpoint, we can run the application and test the health check. Start the application using the following command:

dotnet run

Navigate to <https://localhost:5001/health> in your web browser or use a tool like Postman to send an HTTP GET request to the endpoint. You should see the following response:

Healthy

The default health check indicates that our application is healthy. However, this basic health check doesn’t provide much insight into the actual health of our application. Let’s add some custom health checks to monitor specific components of our application.

Adding Custom Health Checks

Imagine that our application relies on a database and a message queue. We would like to monitor the health of both components. To do this, we can create custom health check classes that implement the IHealthCheck interface.

First, let’s create a custom health check for our database. Create a new file named DatabaseHealthCheck.cs and add the following code:

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

public class DatabaseHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        // Replace with your actual database connection check logic
        bool isHealthy = true;

        if (isHealthy)
        {
            return Task.FromResult(HealthCheckResult.Healthy("Database is healthy"));
        }

        return Task.FromResult(HealthCheckResult.Unhealthy("Database is not healthy"));
    }
}

Next, create a custom health check for our message queue. Create a new file named MessageQueueHealthCheck.cs and add the following code:

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

public class MessageQueueHealthCheck : IHealthCheck
{
  public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken =default)
  {
    // Replace with your actual message queue connection check logic
    bool isHealthy = true;
    if (isHealthy)
        {
            return Task.FromResult(HealthCheckResult.Healthy("Message queue is healthy"));
        }

        return Task.FromResult(HealthCheckResult.Unhealthy("Message queue is not healthy"));
    }
}

Now that we have our custom health checks, we can register them in the ConfigureServices method of our Program.cs file. Update the Main method to include the following code:

using var builder = WebApplication.CreateBuilder(args);

// Add Health Checks and Custom Health Checks
builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>("Database")
    .AddCheck<MessageQueueHealthCheck>("MessageQueue");

var app = builder.Build();

// Add Health Check endpoint
app.MapHealthChecks("/health");

app.MapControllers();

app.Run();

We added our custom health checks by calling the AddCheck method and passing in the type of our health check and a name for it.

Modify endpoint response

You can define a custom method to format the health check results in a JSON format.

Create a new file named CustomHealthCheckResponseWriter.cs in your project directory.

using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;

public static class CustomHealthCheckResponseWriter
{
    public static async Task WriteCustomHealthCheckResponse(HttpContext context, HealthReport report)
    {
        context.Response.ContentType = "application/json";

        var healthCheckResults = new Dictionary<string, object>
        {
            { "status", report.Status.ToString() },
            { "results", new Dictionary<string, object>() }
        };

        foreach (var entry in report.Entries)
        {
            var entryValues = new Dictionary<string, object>
            {
                { "status", entry.Value.Status.ToString() },
                { "description", entry.Value.Description },
                { "duration", entry.Value.Duration.ToString() }
            };

            ((Dictionary<string, object>)healthCheckResults["results"]).Add(entry.Key, entryValues);
        }

        await context.Response.WriteAsync(JsonSerializer.Serialize(healthCheckResults, new JsonSerializerOptions { WriteIndented = true }));
    }
}

Update the Program.cs to use the custom ResponseWriter from the CustomHealthCheckResponseWriter class:

// ...

var app = builder.Build();

// Add Health Check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = CustomHealthCheckResponseWriter.WriteCustomHealthCheckResponse
});

// ...

app.Run();

Run the application again and navigate to https://localhost:5001/health in your web browser or use a tool like Postman to send an HTTP GET request to the endpoint. This time, you should see a JSON response that includes the status of each custom health check:

{
  "status": "Healthy",
  "results": {
    "Database": {
      "status": "Healthy",
      "description": "Database is healthy",
      "duration": "00:00:00.0009610"
    },
    "MessageQueue": {
      "status": "Healthy",
      "description": "Message queue is healthy",
      "duration": "00:00:00.0009615"
    }
  }
}

Using Third-Party Libraries

If you want to extend the health check functionality in your application, you can use third-party libraries like AspNetCore.Diagnostics.HealthChecks. This library provides additional health check implementations for various components, such as databases, caches, and more.

To use this library, first add the appropriate NuGet packages to your project. For example, if you want to add health checks for SQL Server and Redis, you should install the following packages:

dotnet add package AspNetCore.HealthChecks.SqlServer
dotnet add package AspNetCore.HealthChecks.Redis

Next, update your Program.cs file to include the necessary using statements and register the additional health checks:

using HealthChecks.SqlServer;
using HealthChecks.Redis;

// ...

using var builder = WebApplication.CreateBuilder(args);

// Add Health Checks and Custom Health Checks
builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>("Database")
    .AddCheck<MessageQueueHealthCheck>("MessageQueue")
    .AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), name: "SQL Server")
    .AddRedis(builder.Configuration.GetConnectionString("Redis"), name: "Redis");

// ...

Now, when you run your application and navigate to the health check endpoint, you will see the additional health checks in the response.

Adding HealthCheck UI

Another helpful feature provided by the AspNetCore.Diagnostics.HealthChecks library is the HealthCheck UI. This user interface allows you to visualize the health status of your services in a web-based dashboard. The HealthCheck UI can aggregate the health status of multiple services, making it a valuable tool in microservices architecture.

Setting Up HealthCheck UI

To use the HealthCheck UI, you will need to install the following NuGet packages:

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

Next, update your Program.cs file and register the HealthCheck UI:

using HealthChecks.UI.Client;
// ...
builder.Services.AddHealthChecksUI(setupSettings: setup =>
    {
        setup.AddHealthCheckEndpoint("main", "/health");
    })
    .AddInMemoryStorage();
// ...
app.MapHealthChecks("/health", new HealthCheckOptions(){
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// ....
app.MapHealthChecksUI(options =>
{
    options.UIPath = "/health-ui";
});
// ...

In the code above, we added the HealthCheck UI service by calling AddHealthChecksUI() on the Services property of the builder. We also specified the use of in-memory storage for the health check history, which is useful for development purposes.

Run the application again and navigate to https://localhost:5001/health-ui in your web browser. You should now see the HealthCheck UI dashboard, displaying the health status of your registered services.

Conclusion

In this article, we explored how to implement health checks in ASP.NET Core 6.0 using built-in features and third-party libraries like AspNetCore.Diagnostics.HealthChecks. By adding health checks to your application, you can better monitor its components and ensure that it remains stable and performant.

References

https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-6.0 https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks

Meet the geek-tastic people, and allow us to amaze you with what it's like to work with j‑labs!

Contact us