Implementing .NET Core Health Checks

Generally, when we are using any uptime monitoring systems or load balancers, these systems will keep monitoring the health of the application and based on its health condition it will decide to send the request to serve it. For this earlier, we use to create a special endpoint where it will return any error message or code to indicate the health of the API/service.
 
Following is the sample, an endpoint /health where it verifying the database connection and returns the result accordingly.
  1. [Route("health")]  
  2. public ActionResult Health()  
  3. {  
  4.     using (var connection = new SqlConnection(_connectionString))  
  5.     {  
  6.         try  
  7.         {  
  8.             connection.Open();  
  9.         }  
  10.         catch (SqlException)  
  11.         {  
  12.             return new HttpStatusCodeResult(503, "Database connection is unhealthy");  
  13.         }  
  14.     }  
  15.   
  16.     return new EmptyResult();  
  17. }  
When we ran the application with endpoint /health, it will display an empty message with 200 status code and 503 status code when there is any connectivity issue while connecting to the database.
 
 Implementing .NET Core Health Checks
 
Now, based on these resulted status codes, monitoring systems can take appropriate actions like removing this particular services instance from its list of healthy services so that no requests will be redirected to it until it becomes healthy (in our case, when database connectivity issue resolves).
 
We need to keep adding more external resource health checks accordingly.
 
Since .NET Core 2.2, we no need to add a special controller for health check endpoint, instead, the framework itself providing Health Check services as follows.
 

NuGet Package

 
You have to install following NuGet package
 
Install-Package Microsoft.Extensions.Diagnostics.HealthChecks
 
Once the package is installed, we need to add the following lines at ConfigureServices() and Configure() methods in Startup.cs file.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     services.AddHealthChecks();  
  4. }  
  5.    
  6. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  7. {  
  8.     app.UseHealthChecks("/Health");  
  9. }  
As observed, we are providing the endpoint name in Configure() method. These lines of code will enable a dynamic endpoint "/Health" and display either Healthy/UnHealthy results based on its health state.
 
But, where can we write our custom logic to replicate the above? Yes, we have many features to customize our needs from logic to display results.
 

Adding Custom Logic

 
We can perform in two ways the following are those.
 
Option 1
 
In ConfigureServices method,
  1. public void ConfigureServices(IServiceCollection services) {  
  2.  services.AddHealthChecks()  
  3.   .AddCheck("sql", () => {  
  4.   
  5.    using(var connection = new SqlConnection(_connectionString)) {  
  6.     try {  
  7.      connection.Open();  
  8.     } catch (SqlException) {  
  9.      return HealthCheckResult.Unhealthy();  
  10.     }  
  11.    }  
  12.   
  13.    return HealthCheckResult.Healthy();  
  14.   
  15.   });  
  16. }  
Here, we can use an anonymous method to write the custom logic using AddCheck() method. This will expect a HealthCheckResult object as a result. This object will contain 3 options,
  1. Healthy
  2. Unhealthy
  3. Degraded
Based on the result we need to return appropriately so that runtime will return the status code accordingly. For example, in the above code, if database connection passes it will return a 200 status code (Healthy) and 503 status code (Unhealthy) if failed.
 
Option 2 - In a separate class
 
The class should implement an IHealthCheck interface and implement CheckHealthAsync() method as follows,
  1. public class DatabaseHealthCheck: IHealthCheck {  
  2.  public Task < HealthCheckResult > CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken =  
  3.   default) {  
  4.   using(var connection = new SqlConnection(_connectionString)) {  
  5.    try {  
  6.     connection.Open();  
  7.    } catch (SqlException) {  
  8.     return HealthCheckResult.Healthy();  
  9.    }  
  10.   }  
  11.   
  12.   return HealthCheckResult.Healthy();  
  13.   
  14.  }  
  15. }  
Once we created the class, we need to mention this class in ConfigureServices() method using AddTask<T> method as follows by giving some valid unique names.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.      services.AddControllers();  
  4.   
  5.      services.AddHealthChecks()  
  6.           .AddCheck<DatabaseHealthCheck>("sql");  
  7. }  
Now, our code is clean and we can add any number of Health Tasks as above and it will be run in the order how we declared here.
 

Custom Status Code


As discussed above, by default it will send 200 status code if we return Healthy and 503 for Unhealthy. The Healthcheck service even provides scope for us to change this default behavior by providing custom status code using its options object as follows.
  1. var options = new HealthCheckOptions();  
  2. options.ResultStatusCodes[HealthStatus.Unhealthy] = 420;  
  3. app.UseHealthChecks("/Health", options);  
In this example, I replace the status code for the Unhealthy state with 420.
 

Custom Response


The beauty of this tool is, we can even customize our output for more clear detailed information about each Health check task. This will be very useful in case we have multiple health check tasks to analyze which task made the complete service heath status to Unhealthy.
 
We can achieve this via HealthCheckOptions ResponseWriter property.
  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {  
  2.  app.UseHealthChecks("/Health"new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions() {  
  3.   ResponseWriter = CustomResponseWriter  
  4.  });  
  5. }  
  6.   
  7. private static Task CustomResponseWriter(HttpContext context, HealthReport healthReport) {  
  8.  context.Response.ContentType = "application/json";  
  9.   
  10.  var result = JsonConvert.SerializeObject(new {  
  11.   status = healthReport.Status.ToString(),  
  12.    errors = healthReport.Entries.Select(e => new {  
  13.     key = e.Key, value = e.Value.Status.ToString()  
  14.    })  
  15.  });  
  16.  return context.Response.WriteAsync(result);  
  17.   
  18. }  
Now, the above code will display the result in JSON format will all the tasks information. Here, Key represents the Task name we have given (in our case "sql") and value is either Healthy/Unhealthy.
 

Health Check UI


We can even see the health check results on-screen visually by installing the following NuGet package
 
Install-Package AspNetCore.HealthChecks.UI
 
Once installed need to call respective service methods in ConfigureServices() and Configure() methods accordingly.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     services.AddHealthChecksUI();  
  4. }  
  5.   
  6. public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
  7. {  
  8.     app.UseHealthChecksUI();  
  9. }  
Once configured, you can run the application and point to /healthchecks-ui endpoint which display a UI as follows,
 
Implementing .NET Core Health Checks
 
Happy Coding ðŸ˜Š


Similar Articles