Application health monitoring using asp.net core

7 minute read

Introduction

Health monitoring provides real-time information about the state of the application. It’s a very crucial part for large distributed application. Especially, microservies based application. In this article, I will show you how to monitor application’s health in different ways.

Tools and technologies used

  • Visual Studio 2022
  • .NET 6.0
  • SQL Server
  • Entity Framework
  • ASP.NET Core Web API
  • C#

Implementation

Step 1: Create solution and projects.

  • Create a solution name HealthCheck
  • Add 4 new web api project, name - Admin.API, Customer.API, Location.API and WebStatus in the solution.

Here WebStatus project will monitor health of Admin.API, Customer.API and Location.API project and display result on watchdog.

Step 2: Install nuget packages.

  • Install following nuget package in Admin.API Project
PM> Install-Package AspNetCore.HealthChecks.UI.Client
  • Install following nuget packages in Customer.API Project
PM> Install-Package AspNetCore.HealthChecks.SqlServer
PM> Install-Package AspNetCore.HealthChecks.UI.Client
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Newtonsoft.Json
PM> Install-Package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
PM> Install-Package System.Data.SqlClient

  • Install following nuget packages in Location.API Project
PM> Install-Package AspNetCore.HealthChecks.UI.Client
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
PM> Install-Package AspNetCore.HealthChecks.SqlServer

  • Install following nuget packages in WebStatus Project
PM> Install-Package AspNetCore.HealthChecks.UI
PM> Install-Package AspNetCore.HealthChecks.UI.InMemory.Storage

Step 3: Configure Admin.API project for health check

  • Modify Program.cs file is as follows
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register required services for health checks
builder.Services.AddHealthChecks();


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});

app.Run();

  • Here, Registered healthcheck service using
builder.Services.AddHealthChecks();
  • Added healthcheck end point using following code


// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});


  • Now you can run and check the application health using the following URL

    https://localhost:7147/hc

    https://localhost:7147/liveness

Step 4: Configure Customer.API project for health check

  • Add ConnectionString in appsettings.json file as follows
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "CustomerDBConnection": "Server=localhost;Database=Demo.CustomerDB;User Id=sa;Password=secret@pass;"
  },
  "AllowedHosts": "*"
}

  • Add CustomerDbContext class in Customer.API/Db folder CustomerDbContext.cs

using Microsoft.EntityFrameworkCore;

namespace Customer.API.Db
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext(DbContextOptions<CustomerDbContext> options) : base(options)
        {

        }
    }
}


  • Add DatabaseHealthCheck and HealthCheckResponse class in Customer.API/Utility folder

DatabaseHealthCheck.cs


using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Data.SqlClient;

namespace Customer.API.Utility
{
    public class DatabaseHealthCheck : IHealthCheck
    {
        private IConfiguration _configuration;
        public DatabaseHealthCheck(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            
            string _connectionString = _configuration.GetConnectionString("CustomerDBConnection");

            using (var connection = new SqlConnection(_connectionString))
            {
                try
                {
                    connection.Open();
                }
                catch (SqlException)
                {
                    // return HealthCheckResult.Healthy();
                    return await Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, "Cannot connect to Demo.CustomerDB"));
                }
            }

            return HealthCheckResult.Healthy();
        }
    }
}


HealthCheckResponse.cs

using HealthChecks.UI.Client;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;

namespace Customer.API.Utility
{
    public class HealthCheckResponse
    {
        public static Task CustomResponseWriter(HttpContext context, HealthReport healthReport)
        {

            context.Response.ContentType = "application/json";

            var result = JsonConvert.SerializeObject(new
            {
                status = healthReport.Status.ToString(),
                errors = healthReport.Entries.Select(e => new {
                    key = e.Key,
                    value = e.Value.Status.ToString()
                })
            });

            return UIResponseWriter.WriteHealthCheckUIResponse(context, healthReport);
            //return context.Response.WriteAsync(result);
        }
    }
}

  • Modify Program.cs file is as follows
using Customer.API.Db;
using Customer.API.Utility;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);


// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Sql server health check with name "customersql" with custom healtcheck
builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>("customersql");


// Create DbContext
builder.Services.AddDbContext<CustomerDbContext>(options =>
       options.UseSqlServer(
           builder.Configuration.GetConnectionString("CustomerDBConnection"))
    );

builder.Services.AddHealthChecks()
    .AddDbContextCheck<CustomerDbContext>("customerdbcontext");

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

var options = new HealthCheckOptions();
options.ResultStatusCodes[HealthStatus.Healthy] = StatusCodes.Status200OK;
options.ResultStatusCodes[HealthStatus.Degraded] = StatusCodes.Status200OK;
options.ResultStatusCodes[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable;
options.ResponseWriter = HealthCheckResponse.CustomResponseWriter;
options.Predicate = healthcheck => healthcheck.Name == "customersql";

app.UseHealthChecks("/customersql", options);
//.RequireAuthorization();


app.UseHealthChecks("/customerdbcontext", new HealthCheckOptions()
{
    // Supress cache headers
    AllowCachingResponses = false,

    // Customize the HTTP Status code
    ResultStatusCodes =
    {
        [HealthStatus.Healthy] = StatusCodes.Status200OK,
        [HealthStatus.Degraded] = StatusCodes.Status200OK,
        [HealthStatus.Unhealthy]= StatusCodes.Status503ServiceUnavailable
    },

    // filters the health checks so that only those tagged with sql
    Predicate = healthCheck => healthCheck.Name == "customerdbcontext",

    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});


// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    //Predicate = _ => true,
    Predicate = r => r.Name.Contains("self"),
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability
//to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});

app.Run();

  • Here, Checking sql server’s health with a name customersql using following code

builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>("customersql");
  • Here, Checking sql server’s health using dbcontext of EF using following code

builder.Services.AddDbContext<CustomerDbContext>(options =>
       options.UseSqlServer(
           builder.Configuration.GetConnectionString("CustomerDBConnection"))
    );

builder.Services.AddHealthChecks()
    .AddDbContextCheck<CustomerDbContext>("customerdbcontext");

  • Added healthcheck end point for different services seperatedly using the following code

var options = new HealthCheckOptions();
options.ResultStatusCodes[HealthStatus.Healthy] = StatusCodes.Status200OK;
options.ResultStatusCodes[HealthStatus.Degraded] = StatusCodes.Status200OK;
options.ResultStatusCodes[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable;
options.ResponseWriter = HealthCheckResponse.CustomResponseWriter;
options.Predicate = healthcheck => healthcheck.Name == "customersql";

app.UseHealthChecks("/customersql", options);
//.RequireAuthorization();


app.UseHealthChecks("/customerdbcontext", new HealthCheckOptions()
{
    // Supress cache headers
    AllowCachingResponses = false,

    // Customize the HTTP Status code
    ResultStatusCodes =
    {
        [HealthStatus.Healthy] = StatusCodes.Status200OK,
        [HealthStatus.Degraded] = StatusCodes.Status200OK,
        [HealthStatus.Unhealthy]= StatusCodes.Status503ServiceUnavailable
    },

    // filters the health checks so that only those tagged with sql
    Predicate = healthCheck => healthCheck.Name == "customerdbcontext",

    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});


// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    //Predicate = _ => true,
    Predicate = r => r.Name.Contains("self"),
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability
//to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});

  • Now you can run and check the application health using the following URL

    https://localhost:7145/hc

    https://localhost:7145/customersql

    https://localhost:7145/customerdbcontext

    https://localhost:7145/liveness

Step 5: Configure Location.API project for health check

  • Add ConnectionString in appsettings.json file as follows
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "CustomerDBConnection": "Server=localhost;Database=Demo.LocationDB;User Id=sa;Password=secret@pass;"
  },
  "AllowedHosts": "*"
}

  • Add CustomerDbContext class in Customer.API/Persistence folder

LocationDbContext.cs


using Microsoft.EntityFrameworkCore;


namespace Location.API.Persistence
{
    public class LocationDbContext : DbContext
    {
        public LocationDbContext(DbContextOptions<LocationDbContext> options) : base(options)
        {

        }
    }
}



  • Modify Program.cs file is as follows
using HealthChecks.UI.Client;
using Location.API.Persistence;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


// Sql server health check 
builder.Services.AddHealthChecks()
    .AddSqlServer(
        builder.Configuration.GetConnectionString("LocationDBConnection"));

// Create DbContext
builder.Services.AddDbContext<LocationDbContext>(options =>
       options.UseSqlServer(
           builder.Configuration.GetConnectionString("LocationDBConnection"))
    );

// DbContext health check for EF core
builder.Services.AddHealthChecks()
    .AddDbContextCheck<LocationDbContext>("locationdbcontext");

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});

app.Run();

  • Here, Checking sql server’s health using following code

builder.Services.AddHealthChecks()
    .AddSqlServer(
        builder.Configuration.GetConnectionString("LocationDBConnection"));

  • Here, Checking sql server’s health using dbcontext of EF using following code

builder.Services.AddDbContext<LocationDbContext>(options =>
       options.UseSqlServer(
           builder.Configuration.GetConnectionString("LocationDBConnection"))
    );

// DbContext health check for EF core
builder.Services.AddHealthChecks()
    .AddDbContextCheck<LocationDbContext>("locationdbcontext");


  • Added healthcheck end point to check the application health
  • Here application will be healthy when both sql server and application is up and running. Otherwise, it will be un healthy.

Program.cs

using HealthChecks.UI.Client;
using Location.API.Persistence;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


// Sql server health check 
builder.Services.AddHealthChecks()
    .AddSqlServer(
        builder.Configuration.GetConnectionString("LocationDBConnection"));

// Create DbContext
builder.Services.AddDbContext<LocationDbContext>(options =>
       options.UseSqlServer(
           builder.Configuration.GetConnectionString("LocationDBConnection"))
    );

// DbContext health check for EF core
builder.Services.AddHealthChecks()
    .AddDbContextCheck<LocationDbContext>("locationdbcontext");

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// Cofigure for health check
app.MapHealthChecks("/hc", new HealthCheckOptions()
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});

//a basic health probe configuration that reports the app's availability to process requests (liveness) is sufficient to discover the status of the app.
app.MapHealthChecks("/liveness", new HealthCheckOptions()
{
    Predicate = r => r.Name.Contains("self"),
});

app.Run();


  • Now you can run and check the application health using the following URL

    https://localhost:7232/hc

    https://localhost:7232/liveness

Step 6: Configure WebStatus project for to monitors others 3 application’s health

  • Configure HealthCheckUI in appsettings.json file as follows
{
  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "Admin.API",
        "Uri": "https://localhost:7147/hc"
      },
      {
        "Name": "Customer.API",
        "Uri": "https://localhost:7145/hc"
      },
      {
        "Name": "Customer.API -> CustomerDB",
        "Uri": "https://localhost:7145/customersql"
      },
      {
        "Name": "Customer.API -> CustomerDbContext",
        "Uri": "https://localhost:7145/customerdbcontext"
      },

      //Location service
      {
        "Name": "Location.API",
        "Uri": "https://localhost:7232/hc"
      }
    ],

    "EvaluationTimeInSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}


  • Modify Program.cs file is as follows

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Configure service for healthcheck
builder.Services.AddHealthChecksUI().AddInMemoryStorage();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

// Cofigure methods for healthcheck
app.MapHealthChecksUI();

app.MapControllers();

app.Run();


  • Here, Added HealthCheckUI using the following code
// Configure service for healthcheck
builder.Services.AddHealthChecksUI().AddInMemoryStorage();
.
.
.
.
// Cofigure methods for healthcheck
app.MapHealthChecksUI();

  • Now now all(multiple) projects at a time and monitor all application’s

    https://localhost:7188/healthchecks-ui#/healthchecks

Source code