From 34b3eae10f94362a06d9d4abe5ca417c3fa7fe35 Mon Sep 17 00:00:00 2001 From: Jon Galloway Date: Wed, 26 Feb 2025 16:13:48 -0800 Subject: [PATCH 1/3] Add health checks module Fixes #76 Add health checks module to the application. * **Add Health Check Packages**: Add `Microsoft.Extensions.Diagnostics.HealthChecks`, `AspNetCore.HealthChecks.Npgsql`, and `AspNetCore.HealthChecks.Redis` package references to `complete/Api/Api.csproj`. * **Add Health Check Services**: Add health check services for database, redis cache, and external service in `complete/Api/Program.cs`. * **Add Health Check Endpoints**: Add health check endpoints for `/health` and `/alive` in `complete/Api/Program.cs`. * **Considerations for Non-Development Environments**: Add considerations for non-development environments in `MapDefaultEndpoints` method in `complete/ServiceDefaults/Extensions.cs`. * **Documentation**: Add `workshop/10-health-checks.md` to explain the health checks module and include instructions for students to update the application with health checks. Reference relevant documentation and include information on the HealthChecksUI sample. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/dotnet-presentations/dotnet-aspire-workshop/issues/76?shareId=XXXX-XXXX-XXXX-XXXX). --- complete/Api/Api.csproj | 3 + complete/Api/Program.cs | 14 +++ complete/ServiceDefaults/Extensions.cs | 45 ++++++++++ workshop/10-health-checks.md | 117 +++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 workshop/10-health-checks.md diff --git a/complete/Api/Api.csproj b/complete/Api/Api.csproj index 5e5534a..3ac34a0 100644 --- a/complete/Api/Api.csproj +++ b/complete/Api/Api.csproj @@ -9,6 +9,9 @@ + + + diff --git a/complete/Api/Program.cs b/complete/Api/Program.cs index 610e93b..34386ff 100644 --- a/complete/Api/Program.cs +++ b/complete/Api/Program.cs @@ -2,6 +2,7 @@ using OpenTelemetry.Trace; using Api.Data; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); @@ -21,6 +22,12 @@ builder.Services.AddOpenTelemetry() .WithMetrics(m => m.AddMeter("NwsManagerMetrics")); +// Add health check services for database, redis cache, and external service +builder.Services.AddHealthChecks() + .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection"), name: "postgresql") + .AddRedis("localhost:6379", name: "redis") + .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherapi"); + var app = builder.Build(); app.MapDefaultEndpoints(); @@ -30,4 +37,11 @@ // Map the endpoints for the API app.MapApiEndpoints(); +// Add health check endpoints for /health and /alive +app.MapHealthChecks("/health"); +app.MapHealthChecks("/alive", new HealthCheckOptions +{ + Predicate = r => r.Tags.Contains("live") +}); + app.Run(); diff --git a/complete/ServiceDefaults/Extensions.cs b/complete/ServiceDefaults/Extensions.cs index b28c969..4df8569 100644 --- a/complete/ServiceDefaults/Extensions.cs +++ b/complete/ServiceDefaults/Extensions.cs @@ -124,6 +124,51 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) Predicate = r => r.Tags.Contains("live") }); } + else + { + // Considerations for non-development environments + app.MapHealthChecks("/health", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + var result = JsonSerializer.Serialize(new + { + status = report.Status.ToString(), + checks = report.Entries.Select(e => new + { + name = e.Key, + status = e.Value.Status.ToString(), + exception = e.Value.Exception?.Message, + duration = e.Value.Duration.ToString() + }) + }); + await context.Response.WriteAsync(result); + } + }); + + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live"), + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + var result = JsonSerializer.Serialize(new + { + status = report.Status.ToString(), + checks = report.Entries.Select(e => new + { + name = e.Key, + status = e.Value.Status.ToString(), + exception = e.Value.Exception?.Message, + duration = e.Value.Duration.ToString() + }) + }); + await context.Response.WriteAsync(result); + } + }); + } return app; } diff --git a/workshop/10-health-checks.md b/workshop/10-health-checks.md new file mode 100644 index 0000000..85fa5c8 --- /dev/null +++ b/workshop/10-health-checks.md @@ -0,0 +1,117 @@ +# Health Checks + +## Introduction + +In this module, we will add health checks to our application. Health checks are used to determine the health of an application and its dependencies. They can be used to monitor the health of the application and its dependencies, and to determine if the application is ready to accept traffic. + +## Adding Health Checks + +### Step 1: Add Health Check Packages + +First, we need to add the necessary packages to our project. Open the `complete/Api/Api.csproj` file and add the following package references: + +```xml + + + + + +``` + +### Step 2: Add Health Check Services + +Next, we need to add the health check services to our application. Open the `complete/Api/Program.cs` file and add the following code: + +```csharp +builder.Services.AddHealthChecks() + .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection"), name: "postgresql") + .AddRedis("localhost:6379", name: "redis") + .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherapi"); +``` + +### Step 3: Add Health Check Endpoints + +Now, we need to add the health check endpoints to our application. Open the `complete/Api/Program.cs` file and add the following code: + +```csharp +app.MapHealthChecks("/health"); +app.MapHealthChecks("/alive", new HealthCheckOptions +{ + Predicate = r => r.Tags.Contains("live") +}); +``` + +### Step 4: Add Considerations for Non-Development Environments + +Finally, we need to add considerations for non-development environments. Open the `complete/ServiceDefaults/Extensions.cs` file and update the `MapDefaultEndpoints` method as follows: + +```csharp +public static WebApplication MapDefaultEndpoints(this WebApplication app) +{ + if (app.Environment.IsDevelopment()) + { + app.MapHealthChecks("/health"); + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + else + { + app.MapHealthChecks("/health", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + var result = JsonSerializer.Serialize(new + { + status = report.Status.ToString(), + checks = report.Entries.Select(e => new + { + name = e.Key, + status = e.Value.Status.ToString(), + exception = e.Value.Exception?.Message, + duration = e.Value.Duration.ToString() + }) + }); + await context.Response.WriteAsync(result); + } + }); + + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live"), + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + var result = JsonSerializer.Serialize(new + { + status = report.Status.ToString(), + checks = report.Entries.Select(e => new + { + name = e.Key, + status = e.Value.Status.ToString(), + exception = e.Value.Exception?.Message, + duration = e.Value.Duration.ToString() + }) + }); + await context.Response.WriteAsync(result); + } + }); + } + + return app; +} +``` + +## References + +For more information on health checks, see the following documentation: + +- [Health Checks in .NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/health-checks) +- [Health Checks in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks) + +## HealthChecksUI Sample + +You can also add a UI for your health checks using the [HealthChecksUI sample](https://github.com/dotnet/aspire-samples/tree/main/samples/HealthChecksUI). This sample shows how to add the UI as a container and provides a link to the sample for those who are interested. From 46c28e021e73ce0eb98ca932c49d22fae27ffddc Mon Sep 17 00:00:00 2001 From: Jon Galloway Date: Wed, 26 Feb 2025 16:41:37 -0800 Subject: [PATCH 2/3] Update health checks and package references - Added `AspNetCore.HealthChecks.Uris` package. - Removed several outdated package references, including Entity Framework Core and health checks for Npgsql and Redis. - Updated `Microsoft.EntityFrameworkCore.Design` and health check packages to version `9.0.2` and `9.0.0`, respectively. - Removed `using Microsoft.EntityFrameworkCore;` from `NwsManager.cs`. - Updated `Program.cs` to reflect changes in health check services, focusing on Redis and external services. - Restructured `MyWeatherHub.csproj` while maintaining existing properties and added new health check packages. - Modified error handling in `Program.cs` to ensure database context creation outside of development mode. - Updated `Extensions.cs` to include health checks and JSON serialization. --- complete/Api/Api.csproj | 12 ++++---- complete/Api/Data/NwsManager.cs | 1 - complete/Api/Program.cs | 11 ++------ complete/MyWeatherHub/MyWeatherHub.csproj | 34 ++++++++++++----------- complete/MyWeatherHub/Program.cs | 8 +++++- complete/ServiceDefaults/Extensions.cs | 2 ++ 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/complete/Api/Api.csproj b/complete/Api/Api.csproj index 3ac34a0..9e8a324 100644 --- a/complete/Api/Api.csproj +++ b/complete/Api/Api.csproj @@ -6,12 +6,14 @@ + - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/complete/Api/Data/NwsManager.cs b/complete/Api/Data/NwsManager.cs index ade4a2a..aecf047 100644 --- a/complete/Api/Data/NwsManager.cs +++ b/complete/Api/Data/NwsManager.cs @@ -5,7 +5,6 @@ using Api.Data; using Api.Diagnostics; using System.Diagnostics; -using Microsoft.EntityFrameworkCore; namespace Api { diff --git a/complete/Api/Program.cs b/complete/Api/Program.cs index 34386ff..239ef89 100644 --- a/complete/Api/Program.cs +++ b/complete/Api/Program.cs @@ -1,8 +1,4 @@ -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Api.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); @@ -22,11 +18,10 @@ builder.Services.AddOpenTelemetry() .WithMetrics(m => m.AddMeter("NwsManagerMetrics")); -// Add health check services for database, redis cache, and external service +// Add health check services for redis cache and external service builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection"), name: "postgresql") .AddRedis("localhost:6379", name: "redis") - .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherapi"); + .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherApi"); var app = builder.Build(); diff --git a/complete/MyWeatherHub/MyWeatherHub.csproj b/complete/MyWeatherHub/MyWeatherHub.csproj index d92b403..fccb6c1 100644 --- a/complete/MyWeatherHub/MyWeatherHub.csproj +++ b/complete/MyWeatherHub/MyWeatherHub.csproj @@ -1,18 +1,20 @@ - - net9.0 - enable - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + net9.0 + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/complete/MyWeatherHub/Program.cs b/complete/MyWeatherHub/Program.cs index a9993e2..20d9872 100644 --- a/complete/MyWeatherHub/Program.cs +++ b/complete/MyWeatherHub/Program.cs @@ -1,5 +1,7 @@ using MyWeatherHub; using MyWeatherHub.Components; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Npgsql.EntityFrameworkCore.PostgreSQL; var builder = WebApplication.CreateBuilder(args); @@ -17,6 +19,10 @@ builder.AddNpgsqlDbContext(connectionName: "weatherdb"); +// Add health check services for database +builder.Services.AddHealthChecks() + .AddNpgSql(builder.Configuration.GetConnectionString("weatherdb"), name: "postgresql"); + var app = builder.Build(); app.MapDefaultEndpoints(); @@ -28,7 +34,7 @@ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } -else +else { using (var scope = app.Services.CreateScope()) { diff --git a/complete/ServiceDefaults/Extensions.cs b/complete/ServiceDefaults/Extensions.cs index 4df8569..20e2407 100644 --- a/complete/ServiceDefaults/Extensions.cs +++ b/complete/ServiceDefaults/Extensions.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; @@ -7,6 +8,7 @@ using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; +using System.Text.Json; namespace Microsoft.Extensions.Hosting; From 220afba4ae8d64b417ddec0079214692b422f866 Mon Sep 17 00:00:00 2001 From: Jon Galloway Date: Wed, 26 Feb 2025 19:57:24 -0800 Subject: [PATCH 3/3] Refactor health check services and update documentation for MyWeatherHub and API projects --- complete/MyWeatherHub/Program.cs | 5 ++- workshop/10-health-checks.md | 63 ++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/complete/MyWeatherHub/Program.cs b/complete/MyWeatherHub/Program.cs index 20d9872..64bd2e0 100644 --- a/complete/MyWeatherHub/Program.cs +++ b/complete/MyWeatherHub/Program.cs @@ -21,7 +21,10 @@ // Add health check services for database builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("weatherdb"), name: "postgresql"); + .AddNpgSql( + builder.Configuration.GetConnectionString("weatherdb"), + name: "database" + ); var app = builder.Build(); diff --git a/workshop/10-health-checks.md b/workshop/10-health-checks.md index 85fa5c8..c2e5d5f 100644 --- a/workshop/10-health-checks.md +++ b/workshop/10-health-checks.md @@ -8,32 +8,62 @@ In this module, we will add health checks to our application. Health checks are ### Step 1: Add Health Check Packages -First, we need to add the necessary packages to our project. Open the `complete/Api/Api.csproj` file and add the following package references: +First, we need to add the necessary packages to our projects. For the API project, open the `complete/Api/Api.csproj` file and add the following package references: ```xml - - - + + + + +``` + +For the MyWeatherHub project, open the `complete/MyWeatherHub/MyWeatherHub.csproj` file and add the following package references: + +```xml + + + ``` ### Step 2: Add Health Check Services -Next, we need to add the health check services to our application. Open the `complete/Api/Program.cs` file and add the following code: +Next, we need to add the health check services to our applications. + +For the API project, open the `complete/Api/Program.cs` file and add the following code: ```csharp +// Add health check services for redis cache and external service builder.Services.AddHealthChecks() - .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection"), name: "postgresql") .AddRedis("localhost:6379", name: "redis") - .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherapi"); + .AddUrlGroup(new Uri("https://api.weather.gov/"), name: "weatherApi"); ``` -### Step 3: Add Health Check Endpoints +For the MyWeatherHub project, open the `complete/MyWeatherHub/Program.cs` file and add the following code: + +```csharp +// Add health check services for database +builder.Services.AddHealthChecks() + .AddNpgSql(builder.Configuration.GetConnectionString("weatherdb"), name: "postgresql"); +``` -Now, we need to add the health check endpoints to our application. Open the `complete/Api/Program.cs` file and add the following code: +### Step 3: Map Health Check Endpoints + +Now, we need to add the health check endpoints to our applications. + +The ServiceDefaults project already maps default health check endpoints using the `MapDefaultEndpoints()` extension method. This method is provided as part of the .NET Aspire service defaults and maps the standard `/health` and `/alive` endpoints. + +To use these endpoints, simply call the method in your application's `Program.cs` file: + +```csharp +app.MapDefaultEndpoints(); +``` + +If you need to add additional health check endpoints, you can add them like this in the Api's `Program.cs` file: ```csharp +// Add health check endpoints for /health and /alive app.MapHealthChecks("/health"); app.MapHealthChecks("/alive", new HealthCheckOptions { @@ -41,16 +71,21 @@ app.MapHealthChecks("/alive", new HealthCheckOptions }); ``` -### Step 4: Add Considerations for Non-Development Environments +### Step 4: Understand the Default Health Check Implementation -Finally, we need to add considerations for non-development environments. Open the `complete/ServiceDefaults/Extensions.cs` file and update the `MapDefaultEndpoints` method as follows: +The default implementation in ServiceDefaults/Extensions.cs already includes smart behavior for handling health checks in different environments: ```csharp public static WebApplication MapDefaultEndpoints(this WebApplication app) { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. if (app.Environment.IsDevelopment()) { + // All health checks must pass for app to be considered ready to accept traffic after starting app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive app.MapHealthChecks("/alive", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") @@ -58,6 +93,7 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) } else { + // Considerations for non-development environments app.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => true, @@ -105,6 +141,11 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) } ``` +The implementation includes different approaches for development and production environments: + +- In development: Simple endpoints for quick diagnostics +- In production: More detailed JSON output with additional security considerations + ## References For more information on health checks, see the following documentation: