Telerik blogs
DotNetT2 Light_1200x303

With the advent and popularization of web APIs and microservices, monitoring their health is indispensable, and when dealing with multiple APIs at the same time, this task can become quite challenging. But there are some features that help us develop checks quickly and accurately—health checks.

What Are Health Checks in ASP.NET Core?

Health checks are part of the middleware and libraries provided by ASP.NET Core to help report the health of application infrastructure components.

Why Do We Need Health Checks?

Because through them we can monitor the functioning of an application in real time, in a simple and direct way. 👨‍⚕️🩺

How Do Health Checks Work?

To know if an API is running in a healthy way or not, we use a health check to validate the status of a service and its dependencies through an endpoint in the API of the REST service.

This allows us to quickly and in a standardized manner decide if the service or our dependences is off.

This endpoint uses a separate service that assesses the availability of the functions of the rest of the system and also the state of its dependencies.

The information collected during the check can include performance calculations, runtime or connection to other downstream services. After completing the evaluation, an HTTP code and a JSON object are returned depending on the evaluation of the services in the health check.

Now that we know the basics about health checks, we can implement an example and see in practice how it works.

👨‍💻 You can access the full source code here.

Creating the Project

Open a PowerShell console in the folder where you want to create the project, then give the command below. This command will create an ASP.NET 5 Web API project with the name “HealthCheck.” Once it’s created, you can open the file “HealthCheck.csproj” with Visual Studio.

dotnet new webapi -n HealthCheck --framework net5.0

Then create a new controller named “HealthController” and add the following code:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Threading.Tasks;

namespace HealthCheck.Controllers
{
    [Controller]
    [Route("health")]
    public class HealthController : ControllerBase
    {
        private readonly ILogger<HealthController> _logger;
        private readonly HealthCheckService _service;

        public HealthController(ILogger<HealthController> logger, HealthCheckService service)
        {
            _logger = logger;
            _service = service;
        }

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var report = await _service.CheckHealthAsync();

            _logger.LogInformation($"Get Health Information: {report}");

            return report.Status == HealthStatus.Healthy ? Ok(report) : StatusCode((int)HttpStatusCode.ServiceUnavailable, report);
        }
    }
}

Our controller will contain the method responsible for checking the API health. When it receives a request, it will check it, and then it will return a JSON with the response content and, as a good practice, we added a method to log the execution.

Registering the HealthCheck Service

Open the file “Startup.cs” and inside it find the method “ConfigureServices”. Inside it, add this code snippet:

services.AddHealthChecks();

Now you can start the application, and you will see the /health route that we just created. You can use Fiddler to make the request like in the image below:

Get Health By Fiddler Everywhere

When we sent a request to the /health route, the application returned a JSON object with some information:

Entries: A dictionary-type object in this case is empty.

Status: 2 - Derives from the “HealthStatus” enum and as the summary means: “Indicates that the health check determined that the component was healthy.”

TotalDuration: Health Check runtime information.

In simple terms, our API is “healthy”—no problems were found and we now have a route just to check this. 🎉😊

Health Check in Database

In addition to the integrity of the application, we can also check other basic factors such as the connection to a database.

In this case, we will use Redis as a database. You need to have it running on your machine in a Docker image or directly. If you want you can use any other database just change the connection string.

For that, we need to install the package: “AspNetCore.HealthChecks.Redis” - Version=“5.0.2”.

You can install it in the project with NugetPackage or from the console with the command:

dotnet add package AspNetCore.HealthChecks.Redis

Now let’s create a “Helper” class where we’ll put the connection string with Redis. Then create a folder called “Helpers” in the project, and inside it create a static class called “UtilsHelpers” and inside it put the following code:

public static string GetConnectionString()
{
return "localhost:6379";
}

In the file “Startup.cs” add the method responsible for verifying the connection with Redis. It will use the connection string that we just created.

So the Startup’s “ConfigureServices” method should look like this:

 public void ConfigureServices(IServiceCollection services)
        {
            //Here is HealthCheck and the connection to Redis
            services.AddHealthChecks()
                .AddRedis(redisConnectionString: UtilsHelpers.GetConnectionString(), name: "Redis instance");

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
            });
        }

If your connection to Redis or another database you have used is OK, when you make the request through Fiddler, we will get the following result, showing the information about the connection:

Health Redis By Fiddler Everywhere

HealthCheck Through a Graphical Interface

Another interesting function that the ASP.NET Core health checks provide is a graphical interface with a kind of fun dashboard so that we can view the events that took place during the checks and the history of the entire execution.

So, let’s implement it and see this working. For that, we need to install the following dependencies in the project:

  • AspNetCore.HealthChecks.UI
  • AspNetCore.HealthChecks.UI.Client
  • AspNetCore.HealthChecks.UI.InMemory.Storage

Now at the “Startup” of the project, we will add the configurations of the libraries that we just installed in the project, then replace the method “ConfigureServices” with this one below. You can see in the comments the responsibility of each configuration.

        public void ConfigureServices(IServiceCollection services)
        {
            //Here is HealthCheck and the connection to Redis
            services.AddHealthChecks()
                .AddRedis(redisConnectionString: UtilsHelpers.GetConnectionString(), name: "Redis instance");

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
            });

            // Here is the GUI setup and history storage
            services.AddHealthChecksUI(options =>
            {
                options.SetEvaluationTimeInSeconds(5); //Sets the time interval in which HealthCheck will be triggered
                options.MaximumHistoryEntriesPerEndpoint(10); //Sets the maximum number of records displayed in history
                options.AddHealthCheckEndpoint("Health Checks API", "/health"); //Sets the Health Check endpoint
            }).AddInMemoryStorage(); //Here is the memory bank configuration
        }

And the “Configure” method replace it with this:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "HealthCheck v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();

                //Sets the health endpoint
                endpoints.MapHealthChecks("/health");
            });

            //Sets Health Check dashboard options
            app.UseHealthChecks("/health", new HealthCheckOptions
            {
                Predicate = p => true,
                ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
            });

            //Sets the Health Check dashboard configuration
            app.UseHealthChecksUI(options => { options.UIPath = "/dashboard"; });
        }

Our dashboard is almost ready to work. Now, in the “UtilsHelpers” class, add the following method:

 public static string ToJSON(this object @object) => JsonConvert.SerializeObject(@object, Formatting.None);

🔴 Important! To use the “SerializeObject” method, you need to install “Newtonsoft.Json” - Version=“13.0.1” as dependency.

And now, in the “HealthController” replace the “Get” method with this:

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var report = await _service.CheckHealthAsync();
            var reportToJson = report.ToJSON();

            _logger.LogInformation($"Get Health Information: {reportToJson}");

            return report.Status == HealthStatus.Healthy ? Ok(reportToJson) : StatusCode((int)HttpStatusCode.ServiceUnavailable, reportToJson);
        }

Finally, we can see our dashboard. For that, start the application and go to “localhost:PORT/dashboard”. If you followed all the previous steps, you will see in your browser this beautiful dashboard with the verification data:

Health Check Dashboard

Customizing the Checks

An interesting feature is the possibility of customizing the checks, choosing how we want to return the results.

For this we need to create a class that will inherit the interface “IHealthCheck”. Then create a folder called “Custom” and inside it create a class named “CustomHealthChecks” and put the code below in it:

using Microsoft.Extensions.Diagnostics.HealthChecks;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace HealthCheck.Custom
{
    public class CustomHealthChecks : IHealthCheck
    {
        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            var catUrl = "https://http.cat/401";

            var client = new HttpClient();

            client.BaseAddress = new Uri(catUrl);

            HttpResponseMessage response = await client.GetAsync("");

            return response.StatusCode == HttpStatusCode.OK ? 
                await Task.FromResult(new HealthCheckResult(
                      status: HealthStatus.Healthy,
                      description: "The API is healthy (。^▽^)")) :
                await Task.FromResult(new HealthCheckResult(
                      status: HealthStatus.Unhealthy, 
                      description: "The API is sick (‘﹏*๑)"));
        }
    }
}

In this class, we are creating a method that makes a request to a “cat” API and returns the result with a funny Kaomoji.

But it’s not done yet—we need to do the class dependency injection.

So at startup, we’re going to add the check in the class right after the Redis check, so the “ConfigureServices” method should look like this:

       public void ConfigureServices(IServiceCollection services)
        {
            //Here is HealthCheck and the connection to Redis
            services.AddHealthChecks()
                .AddRedis(redisConnectionString: UtilsHelpers.GetConnectionString(), name: "Redis instance")
                .AddCheck<CustomHealthChecks>("Custom Health Checks"); //Here is the custom class dependency injection

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "HealthCheck", Version = "v1" });
            });

            // Here is the GUI setup and history storage
            services.AddHealthChecksUI(options =>
            {
                options.SetEvaluationTimeInSeconds(5); //Sets the time interval in which HealthCheck will be triggered
                options.MaximumHistoryEntriesPerEndpoint(10); //Sets the maximum number of records displayed in history
                options.AddHealthCheckEndpoint("Health Checks API", "/health"); //Sets the Health Check endpoint
            }).AddInMemoryStorage(); //Here is the memory bank configuration
        }

Now we can start the application again and we will have the following result in the dashboard:

Health Check Dashboard Custom

Conclusion

In this article, we saw the importance of doing the health check in our APIs and how to implement it in a simple way with health check resources. We also learned how to use the dashboard’s graphical interface and customize the results.

I hope this article is helpful in creating your health checks! See you soon.


assis-zang-bio
About the Author

Assis Zang

Assis Zang is a software developer from Brazil, developing in the .NET platform since 2017. In his free time, he enjoys playing video games and reading good books. You can follow him at: LinkedIn and Github.

Related Posts

Comments

Comments are disabled in preview mode.