Implementing .NET Core 2.2 health checks
.NET Core 2.2 health checks are introduced right out of the box and with minimal work required in order to get them up and running.
If you don’t already have health checks, it’s something to strongly consider. Knowing whether your application is healthy or not can minimise risk by reducing downtime as well as investigation time, and it can alert you when services go down.
If you do have health checks that you’ve implemented yourself, switching to the native .NET Core implementation will be a breeze.
Nuget packages
You’ll want to install the following nuget package before we begin:
Install-Package Microsoft.Extensions.Diagnostics.HealthChecks
Our previous health checks
Previously in our application, we implemented health checks ourselves. This involved:
- A health check HTTP controller
- A health check response model
- A masterclass containing the logic for each health check test; each defined in separate methods
Controller
Health check response model
Logic class
Switch to .NET Core health check
The route for our health check was /healthcheck, so the first step here was to delete our controller to remove the current endpoint. After doing this, booting up the application and navigating to /healthcheck, we were presented with a 404 Not Found. Perfect.
Now we can create this endpoint again, but using the native .NET Core implementation. Creating this endpoint requires two lines of code in your Startup.cs
class.
That’s it. Restart your application and navigate to that endpoint. It should be up and running, and returning healthy.
Custom logic
We aren’t using any custom logic yet to check items that we care about. This could include our API Dependencies, or our database connections etc.
This means that at present, all we know is that our application is running.
Let’s say we have three checks we’d like to do. Two API dependencies and one database connection check. Each check should be split into its own class.
So, we create a class for our database check — and we make it implement the IHealthCheck
interface that comes with .NET Core 2.2. This interface will give you a CheckHealthAsync
method to implement, and this is where your logic goes to check the health of this service.
Here we have implemented the same logic that we had previously. However, we’ve removed a few things.
Firstly, we were previously using a stopwatch to monitor the response time for this health check. .NET Core automatically times our check for us, so we don’t have to implement our own logic for this.
We’re also not returning unhealthy if we hit an exception. .NET knows that if we’re throwing exceptions in a health check, that this isn’t healthy. It’ll automatically return unhealthy if this happens. This also gives us more potential data, as we can present any exceptions in our health check response if we choose to.
We also have the option of returning HealthCheckResult.Unhealthy()
If we add extra logic, we also can return HealthCheckResult.Degraded()
These HealthCheckResult
objects also allow us to explicitly pass in Description
, Exception
and Data
(Data is a dictionary of key-value pairs). All of this can be retrieved and used on our health check page to present more information, which can help us find the causes of unhealthiness.
return HealthCheckResult.Degraded(“Description”, exception, new Dictionary<string, object> { { “foo”, “bar” }});
Adding your health checks
We’ve created separate classes for our separate checks, which all implement IHealthCheck
and we have our logic in place, now what?
Return to your Startup.cs
where we call services.AddHealthChecks()
We can chain our health checks onto this call, by passing in our class with implements IHealthCheck
, and a name to identify what we are checking.
How much cleaner is that than the previous large health check class we had before?
Custom response body
Now if we restart our application, and navigate to /healthcheck, we are still presented with healthy (providing your logic is returning healthy for all your checks). Now that’s great, but it’s not very informative. We want to customise our response.
We opted for a JSON response, showing each check with its name, health status, response time, and then the overall time that the health check took.
To do this, we need to create a HealthCheckOptions
object, with custom ResponseWriter
logic. It looks like this:
This is fully customisable. You can access the descriptions, exceptions and dictionary data that we mentioned earlier. But this wasn’t a requirement for us.
And to use these options:
app.UseHealthChecks(“/healthcheck”);
becomes:
app.UseHealthChecks(“/healthcheck”, healthCheckOptions);
Restart your application and navigate to /healthcheck
Standard logic
Are you wanting a simple check? Maybe test an endpoint is running, or a database is up, and that’s it? There are a variety of Nuget packages available that allow you to test some common platforms easily, without implementing your own IHealthCheck
classes!
Install-Package AspNetCore.HealthChecks.SystemInstall-Package AspNetCore.HealthChecks.NetworkInstall-Package AspNetCore.HealthChecks.SqlServerInstall-Package AspNetCore.HealthChecks.MongoDbInstall-Package AspNetCore.HealthChecks.NpgsqlInstall-Package AspNetCore.HealthChecks.ElasticsearchInstall-Package AspNetCore.HealthChecks.RedisInstall-Package AspNetCore.HealthChecks.EventStoreInstall-Package AspNetCore.HealthChecks.AzureStorageInstall-Package AspNetCore.HealthChecks.AzureServiceBusInstall-Package AspNetCore.HealthChecks.AzureKeyVaultInstall-Package AspNetCore.HealthChecks.MySqlInstall-Package AspNetCore.HealthChecks.DocumentDbInstall-Package AspNetCore.HealthChecks.SqLiteInstall-Package AspNetCore.HealthChecks.RavenDBInstall-Package AspNetCore.HealthChecks.KafkaInstall-Package AspNetCore.HealthChecks.RabbitMQInstall-Package AspNetCore.HealthChecks.OpenIdConnectServerInstall-Package AspNetCore.HealthChecks.DynamoDBInstall-Package AspNetCore.HealthChecks.OracleInstall-Package AspNetCore.HealthChecks.UrisInstall-Package AspNetCore.HealthChecks.Aws.S3Install-Package AspNetCore.HealthChecks.ConsulInstall-Package AspNetCore.HealthChecks.Hangfire
And the implementation is as easy as:
All working
If everything is working as expected, the last step is to remove your old redundant code and enjoy your new health check.
— Tom Longhurst is a Quality Assurance Engineer in ASOS Tech.