Application health monitoring using asp.net core
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