Service availability
.NET .NET Core Architecture Best Practices C# Diagnostics Patterns Visual Studio Web API

Implementation of Web API Health Checks with .NET Core

Welcome to today’s post.

In today’s post I will be explaining what an application health check is and then show how to implement a basic health check for an ASP.NET Core Web API service.

Before I get to showing you how to setup and configure health checks in an ASP.NET Core application, I will explain what a health check means for an application.

What is an Application Health Check?

What is an application health check?

An application health check is a series of checks or verifications on application dependencies that are required for the application to function. Without the functioning of these dependencies, the application interface and/or reports will not function as expected.

Examples of verifications that we can undertake on each dependencies include the following checks:

  1. Ensure at least one HTTP requests such as an HTTP GET works on at least one Web API method.
  2. Ensure any dependent database connections, such as SQL databases are online and accessible. At a minimum, expect at least one basic SQL SELECT can be performed.
  3. Ensure any other dependent services are online and available. This can include cloud-based queues and storage accounts.

For an application to pass a health check, all it’s dependencies, including API services must be online and pass a minimal health check. Below is the scenario where all dependent services are online and available:

API Health Check

If one of the dependent services is unavailable or offline, then the application would then report the status to the calling client application, which would then report this to the end-user and disable various client application features. The client application could also be a support application that is a dashboard of indicators that show which applications, APIs and services are available and online, and which applications are offline.

API Health Check Failure

As we can see, health check diagnostics are a very powerful pattern that can be used not only by developers to dynamically present applications according to available services, but also as a DevOps tool to report available services to support and infrastructure teams, and then conditionally enable offline services or report offline services to support managers for statistical tracking of availability.

Setup of Application Health Check Diagnostics in.NET Core

To setup health check diagnostics, include the following NuGet package:

Microsoft.Extensions.Diagnostics.HealthChecks

For a .NET Core 3.1 Web API application, we install version 3.1.0 of the above package. The following packages are then installed:

Microsoft.Extensions.Diagnostics.HealthChecks.3.1.0
Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.3.1.0

The package when searched in the Visual Studio Nuget Package Manager is shown below:

In the .NET Core start up service configuration, the health check for HTTP service availability can be checked with the following call to the AddHealthChecks() extension method:

services.AddHealthChecks()

Additional health checks for dependent services can be chained from an IHealthCheck instance with the AddCheck() extension method.

The parameters of the AddCheck() extension method are as shown:

ParameterDescription
IHealthChecksBuilder builderHealth Checks builder instance
string nameName of the health check
IHealthCheck instanceCustom implementation of a health check
HealthStatus? failureStatusFailure status enumerated type
IEnumerable<string> tagsA list of one or more filtered tags

The health status types for the enumerated type HealthStatus are shown below:

Unhealthy
Degraded
Healthy

Implementation of an Application Health Check in ASP.NET Core

A custom implementation of a health check for the SQL server database connection extending the IHealthCheck interface is shown below:

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace BookLoan.Catalog.API.Services
{
    public class SQLDbConnHealthCheck : IHealthCheck
    {
        private static readonly string DefaultTestQuery = "Select 1";

        public string ConnectionString { get; }

        public string TestQuery { get; }

        public SQLDbConnHealthCheck(string connectionString): 
 		this(connectionString, testQuery: DefaultTestQuery)
        {
        }

        public SQLDbConnHealthCheck(string connectionString, string testQuery)
        {
            ConnectionString = connectionString ?? 
                throw new ArgumentNullException(nameof(connectionString));
        }

        public async Task<HealthCheckResult> CheckHealthAsync(
 		    HealthCheckContext context, 
 		    CancellationToken cancellationToken = default)
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                try
                {
                    await connection.OpenAsync(cancellationToken);

                    if (TestQuery != null)
                    {
                        var command = connection.CreateCommand();
                        command.CommandText = TestQuery;

                        await command.ExecuteNonQueryAsync(cancellationToken);
                    }
                }
                catch (DbException ex)
                {
                    return new HealthCheckResult(
                        status: context.Registration.FailureStatus, 
                        exception: ex
                    );
                }
            }

            return HealthCheckResult.Healthy();
        }
    }
}

Much of the code for the custom health check implementation is taken from the Microsoft architecture site here.

The AddCheck() extension call to the above SQL health check class is as follows:

public void ConfigureServices(IServiceCollection services)
{
    …
    // Add health-checks for HTTP and SQL Database..
    services.AddHealthChecks()
       	.AddCheck(
            "CatalogDB-check",
                new SQLDbConnHealthCheck(Configuration["ConnectionString"]),
                HealthStatus.Unhealthy,
                new string[] { "catalogdb" }
        );
    …
}

If the SQL health check is unsuccessful, then the returned status is:

HealthStatus.Unhealthy

Our API service can then support the endpoint /hc to run the health check as follows:

public void Configure(IApplicationBuilder app, IHostEnvironment env, 
 	ILogger<Startup> logger)
{
    app.UseEndpoints(endpoints =>
    {
       	endpoints.MapHealthChecks("/hc"); // health check endpoint.
        …
    });
    …
}

Testing Health Check Endpoints in ASP.NET Core

To test the above health check endpoint within our Web API service, we can try the following external changes to our dependencies or health-check classes:

  1. A failed SQL query call to the database.
  2. Stopping the SQL server instance.

Note that if we stop the App Pool of the Web API within IIS, the response we get from the health check will be a 503 (service unavailable) error.  

The health-check can be tested from either the browser or from an API testing tool such as POSTMAN.

Below is the success scenario for a positive health-check from the Chrome browser:

Below is the positive health-check from POSTMAN calling the IIS hosted Web API endpoint:

http://localhost/BookLoan.Catalog.API/hc

When we stop the SQL server instance service as shown:

The resulting health-check will be unhealthy from a browser as shown:

The corresponding failed health-check from POSTMAN is as shown:

To test a failed SQL query, we can pass a test query to the SQL health check as shown:

services.AddHealthChecks()
    .AddCheck(
       	"CatalogDB-check",
            new SqlDbConnHealthCheck(
                Configuration.GetConnectionString(connStr), 
                "SELECT * FROM abc"),
                HealthStatus.Unhealthy,
                new string[] { "catalogdb" }
    );

Where the query “SELECT * FROM abc” is the faulty SQL referring to a non-existent table.

When testing availability and connectivity to a SQL instance, it is recommended to only use the minimal SQL to test that the SQL instance is running. Testing a SQL call to a specific table object could be used to test that the credentials of the SQL connection have sufficient security grants to be able to access the objects referred within the SQL command.

In addition, if you have other sections of your start up dependent on the SQL connection, like Entity Framework initialisation, ASP.NET Core Identity initialisation, or even database seeding or database schema migration, then these will need to be wrapped within an exception block and logged in the event an error occurs. Without these protections, your API will break with an unhandled 500.x error before the health checks are performed.

By combining these health-checks we can produce quite useful dashboards that can give a snapshot of the availability of a series of dependent API microservices for a web and web API application.

That is all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial