ASP.NET Core health check development began in fall 2016. It was an architecture draft at the time. In November 2016, the related testing feature was released, so follow me to learn about it.

1. What’s the use of a health check?

Imagine that you are creating an ASP.NET Core application that relies heavily on some subsystem, such as a database, a file system, an API, or something similar.

This is a very common situation, and almost every application relies on a database.

If the connection to the database is lost for any reason, the application is bound to break.

For years, nothing seemed to be done, and while you can imagine ASP.NET health checks being useful, that’s not the real reason they were developed.

So let’s continue with the database scenario as an example.

  • What if you could check that the database was available before you actually connected?
  • What if you could tell your application to display a user-friendly message that the database is not available?
  • What if you could simply switch to the standby database if the actual standby database was not available?
  • If your application is not performing properly due to the lack of a database, what if you could tell the load balancer to switch to another backup environment?

You can do this using a health check:

Check the health and availability of subsystems, provide an address to inform other systems about the health of the current application, and have the health of other systems check that address.

The health check is performed for the microservice environment.

Loosely-coupled applications need to know the health of the systems on which they depend. But it is also useful in more monolithic applications that rely on certain subsystems and infrastructures.

2. How do I enable health check?

The health check is already in the framework and you can use it without adding a separate NuGet package. Microsoft. Extensions. Diagnostics and HealthChecks, it should already be in the package.

To enable health checks, you need to add related services to the DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
    services.AddControllersWithViews();
}
Copy the code

To also provide an endpoint to tell other applications about the current state of the system, you need to map routes to checks within the Configure method of the Startup class:

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

This will provide a URL where you can check the health of your application.



Our application is absolutely healthy because it doesn’t do anything.

3. Write the check content

There are many ways to add health checks. The easiest and best way to see how it works is to use the lambda method:

services.AddHealthChecks()
    .AddCheck("Foo", () =>
        HealthCheckResult.Healthy("Foo is OK!"), tags: new[] { "foo_tag" })
    .AddCheck("codeex", () =>
        HealthCheckResult.Degraded("codeex is somewhat OK!"), tags: new[] { "bar_tag" })
    .AddCheck("webmote", () =>
        HealthCheckResult.Unhealthy("webmote is not OK!"), tags: new[] { "foobar_tag" });
Copy the code

These lines add three different health checks. They are called different names, and the actual check is a Lambda expression that returns a specific HealthCheckResult. The result could be “healthy”, “degraded” or “unhealthy”.

Typically, health check results have at least one label that can group them by topic or other content. The message should have meaning to easily identify the actual problem.

These are just a few demos, but they show how health checks work. If we run the application again and invoke the endpoint, we see the abnormal state because it always displays the worst state, which is abnormal.

Now, let’s demonstrate a more useful health check. Ping the required resources over the Internet and check availability:

services.AddHealthChecks()
    .AddCheck("ping", () = > {try
        {
            using (var ping = new Ping())
            {
                var reply = ping.Send("asp.net-hacker.rocks");
                if(reply.Status ! = IPStatus.Success) {return HealthCheckResult.Unhealthy("Ping is unhealthy");
                }

                if (reply.RoundtripTime > 100)
                {
                    return HealthCheckResult.Degraded("Ping is degraded");
                }

                return HealthCheckResult.Healthy("Ping is healthy");
            }
        }
        catch
        {
            return HealthCheckResult.Unhealthy("Ping is unhealthy"); }});Copy the code

Of course, this requires ping support and certain permissions, which I suspect most likely won’t work, as this is still a demo.

Of course, this chunk of code looks bad in the Startup class, and yes, you need to do your health check in a different way.

public class ExampleHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var healthCheckResultHealthy = true;

        if (healthCheckResultHealthy)
        {
            return Task.FromResult(
                HealthCheckResult.Healthy("A healthy result."));
        }

        return Task.FromResult(
            HealthCheckResult.Unhealthy("An unhealthy result.")); }}Copy the code

The reference class is the following statement.

services.AddHealthChecks()
    .AddCheck<ExampleHealthCheck>("webmotebased".null.new[] { "label" });
Copy the code

We also need to specify a name and at least one label. With the second parameter, I can set the default failure state. Of course, I can use NULL if I can handle all the exceptions in the health check.

4. Send more health details

As mentioned earlier, I can provide an endpoint that exposes my health to systems that depend on the current application. By default, however, it responds with a simple string of simple states.

HealthCheckOptions:

app.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/health".new HealthCheckOptions()
    {
        Predicate = _ => true,
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id? }");
});
Copy the code

Using predicates, you can filter specific health checks to execute and get their status.

Here, I want to do it all. In ResponseWriter you need to write health information for special checks.

UIResponseWriter NuGet: healthchecks.ui.Client, from Github.

The UIResponseWriter project writes JSON output to the HTTP response, including many details.

If the overall status is abnormal, the endpoint sends the result in HTTP response status 503, otherwise 200. This is useful if you only want to deal with HTTP response status.

5. Handle state within the application

In most cases, you don’t want to expose state only to consumers of your application.

In some cases, you may need to deal with different states of the application by displaying a message in case the application does not work properly, disabling the parts of the application that do not work properly, switching to a backup source, or something else. Or you need to run the application in a degraded state.

For this, you can use HealthCheckService, which you can inject at any location using IHealthCheckService.

Let’s see how it works:

public class HomeController : Controller
{
    private readonly IHealthCheckService _healthCheckService;

    public HomeController(
        IHealthCheckService healthCheckService)
    {
        _healthCheckService = healthCheckService;
    }

    public async Task<IActionResult> Health()
    {
        var healthReport = await _healthCheckService.CheckHealthAsync();
        
        return View(healthReport);
    }
Copy the code

You can pass the predicate to method CheckHealthAsync(). Using predicates, you can filter specific health checks. In the code, you execute them all.

You can also create Health. CSHTML to display the results

@using Microsoft.Extensions.Diagnostics.HealthChecks;
@model HealthReport

@{
    ViewData["Title"] = "Health";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's health.

@Model.Status - Duration: @Model.TotalDuration.TotalMilliseconds

    @foreach (var entry in Model.Entries) {
  • @entry.Value.Status - @entry.Value.Description

    Tags: @String.Join(", ", entry.Value.Tags)

    Duration: @entry.Value.Duration.TotalMilliseconds
  • }
Copy the code

6. Beautiful health interface

HealthChecks also provides a nice UI that displays the results in an easy-to-understand way.

Just do some configuration in startup. cs

services.AddHealthChecksUI();
Copy the code

Inside the Configure() method, you need to map the health:

endpoints.MapHealthChecksUI();
Copy the code

This adds a new path for our application to call UI: /healthchecks-ui

We also need to register our health API to the UI by setting appSetings.json:

{... ."HealthChecksUI": {
   "HealthChecks": [{"Name": "HTTP-Api"."Uri": "https://localhost:5001/health"}]."EvaluationTimeOnSeconds": 10."MinimumSecondsBetweenFailureNotifications": 60}}Copy the code

This way, you can register as many health endpoints as you need with the UI.

This application simply shows the health of all your microservices. Let’s take a look at the user interface/Healthchecks-UI



Wow, this is great. This is a nice user interface that shows the health of all the services.

7, summary

Health checks are definitely something you should look into, especially for people developing microservices!

No matter what kind of Web application you’re writing, it can help you create more stable applications. Knowing their health can handle the degradation of a poor state in a way that does not break the entire application.

At least from my point of view, it’s very useful!