ASP.NET Core Health Checks Explained
This is part 7 in our series about ASP.NET Core:
- Part 1: AppSettings in ASP.NET Core
- Part 2: Config transformations in ASP.NET Core
- Part 3: Configuration with Azure App Services and ASP.NET Core
- Part 4: ASP.NET Core Logging Tutorial
- Part 5: Error Logging Middleware in ASP.NET Core
- Part 6: ASP.NET Core Routing Tutorial
- Part 7: ASP.NET Core Health Checks Explained
ASP.NET Core 2.2 introduces a range of new features. One of the more interesting (IMO) is Health Checks. You may use tools like Pingdom or elmah.io Uptime Monitoring to ping your website in a specified interval. Pinging a single HTML page may or may not reveal if your application is healthy or not. Health Checks to the rescue! Before trying out the code yourself, make sure to install the recent version of ASP.NET Core 2.2 and Visual Studio 2017.
When developing a new service, I typically define a custom route (like /working
). This endpoint is requested by Uptime Monitoring, and a notification is sent if things stop working. How /working
is implemented, is up to each API. Some APIs may require a database connection, while others need something completely else. Until now, one of my custom health endpoints could have looked something like this:
[Route("working")]
public ActionResult Working()
{
using (var connection = new SqlConnection(_connectionString))
{
try
{
connection.Open();
}
catch (SqlException)
{
return new HttpStatusCodeResult(503, "Generic error");
}
}
return new EmptyResult();
}
In this example, the /working
endpoint returns 503
if we couldn't open a connection to a SQL Server, but could be another database or any other resource.
With the new Health Checks feature in ASP.NET Core 2.2, health checks no longer require you to create new controllers like in the example above. Health checks are enabled and configured through Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHealthChecks();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseHealthChecks("/working");
...
}
With the sample above, a new endpoint named /working
can be requested by your preferred uptime monitoring system or simply through the browser:
In this example, requesting the endpoint will return a status code 200
together with the body Healthy
, indicating that the API is reachable. If we want to replicate the controller action in the first example, we have a couple of options. Health checks that you only need in a single API can be specified directly as a new check:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddHealthChecks()
.AddCheck("sql", () =>
{
using (var connection = new SqlConnection(_connectionString))
{
try
{
connection.Open();
}
catch (SqlException)
{
return HealthCheckResult.Unhealthy();
}
}
return HealthCheckResult.Healthy();
});
...
}
Requesting the /working
endpoint automatically executes all lambdas provided to invocations of the AddCheck
-method. In the case of a SqlException
thrown inside the health check, a status code of 503
and body Unhealthy
is returned:
A better approach is to write a re-usable health check, contained in its class:
public class SqlServerHealthCheck : IHealthCheck
{
SqlConnection _connection;
public string Name => "sql";
public SqlServerHealthCheck(SqlConnection connection)
{
_connection = connection;
}
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
connection.Open();
}
catch (SqlException)
{
return HealthCheckResult.Unhealthy();
}
return HealthCheckResult.Healthy();
}
}
Configuring the new health check only requires a single line of code:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddHealthChecks()
.AddCheck<SqlServerHealthCheck>("sql");
...
}
Both lambda-based and IHealthCheck
-based checks can be mixed and matched. When adding multiple checks, ASP.NET Core executes each check in the order they were added. All checks needs to return HealthCheckResult.Healthy()
in order for the request to be successful. This means that a single failing health check, will cause an Unhealthy
response.
Health checks are a great addition to an already excellent platform. In time, health checks will (hopefully) exist for most possible dependencies. In fact, the BeatPulse project already migrated their checks to ASP.NET Core Health Checks. Checks for popular databases like SQL Server, MySQL and Oracle already exists. I hope for .NET compatible software as well as the community to pick up the task of writing reusable health checks for all sorts of resources.
Dependency injection
As shown in the code for SqlServerHealthCheck
, health checks added using the AddCheck<T>
-method can use dependency injection, just as you know if from other areas of ASP.NET Core. Health checks are created as transient objects, meaning that a new instance is created every time the /working
endpoint is requested. If you want another lifecycle, health checks can be added manually like this:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddHealthChecks()
.AddCheck<SqlServerHealthCheck>("sql");
services.AddSingleton<SqlServerHealthCheck>();
...
}
Here, the SqlServerHealthCheck
is added as a singleton.
Custom status codes
Health checks comes with a range of status codes out of the box. Like 503
for unhealthy responses. In some cases you may want to change the codes, in order to make the health endpoint work with a client expecting a certain status code.
To change a status code, you can provide custom options when calling the UseHealthChecks
-method:
var options = new HealthCheckOptions();
options.ResultStatusCodes[HealthStatus.Unhealthy] = 418;
app.UseHealthChecks("/working", options);
In this example, I replace the status code for the Unhealthy
state with 418
(I'm a teapot).
Custom response
Much like the returned status codes, the body of the response can be customized as well:
var options = new HealthCheckOptions();
options.ResponseWriter = async (c, r) =>
{
c.Response.ContentType = "application/json";
var result = JsonConvert.SerializeObject(new
{
status = r.Status.ToString(),
errors = r.Entries.Select(e => new { key = e.Key, value = e.Value.Status.ToString() })
});
await c.Response.WriteAsync(result);
};
app.UseHealthChecks("/working", options);
In the example, a JSON object containing the overall status and status of each health check is returned.
I expect Microsoft and/or the community to come up with multiple health check response writers. A writer able to serialize the result to draft-inadarei-api-health-check-02 or rfc7807 would make a lot of sense.
Publishing health check results
If you are using an uptime monitoring solution like elmah.io, we automatically log the response from the /working
endpoint. In some scenarios, publishing health check results directly from your application may be a better option.
ASP.NET Core Health Checks provide a concept named Publishers. By setting up one or more publishers, health check results can be stored in an internal or external storage like Application Insights or elmah.io.
To configure elmah.io as storage for health check results, install the Elmah.Io.AspNetCore.HealthChecks
NuGet package:
Install-Package Elmah.Io.AspNetCore.HealthChecks -IncludePrerelease
Then add the elmah.io publisher:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddHealthChecks()
// Configure your health checks here
.AddElmahIoPublisher("API_KEY", new Guid("LOG_ID"));
...
}
All degraded or unhealthy results are now persisted in elmah.io, where additional notification rules can be configured. For more information about our integration with health checks, check out Publishing ASP.NET Core 2.2 health check results to elmah.io.
More of an Application Insights kind of guy? Luckily, AI is supported as well:
Install-Package AspNetcore.HealthChecks.Publisher.ApplicationInsights
Then configure the AI publisher:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddHealthChecks()
// Configure your health checks here
.AddApplicationInsightsPublisher();
...
}
Visualizing health checks
If you want your health check results visualized as more than a list of errors, there's a nice open source visualization too named HealthChecksUI.
To enable HealthChecksUI, install the AspNetCore.HealthChecks.UI
package:
Install-Package AspNetCore.HealthChecks.UI
Enable the UI in Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecksUI();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHealthChecksUI();
}
HealthChecksUI can be accessed on /healthchecks-ui
:
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website