From 292b6e9f363f068bd0bd687577034da4ca889fa6 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 2 Nov 2026 10:11:14 +0100 Subject: [PATCH] feat(api): expose /health endpoint and health dashboard Wire the infrastructure health checks into ASP.NET Core: - /health returns JSON via UIResponseWriter and maps Unhealthy to HTTP 503 - /health-ui renders a Razor Pages dashboard with per-check status, description, tags and duration - Tag health checks (rabbitmq, workers, ready, live) for monitoring filters - Link the dashboard from the Swagger description and the page layout --- .../DigitalData.MessagingService.API.csproj | 5 +- .../Pages/HealthDashboard.cshtml | 68 +++++++++++++++++++ .../Pages/HealthDashboard.cshtml.cs | 34 ++++++++++ .../Pages/_Layout.cshtml | 28 ++++++++ .../Pages/_ViewImports.cshtml | 3 + .../Program.cs | 28 ++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml create mode 100644 src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs create mode 100644 src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml create mode 100644 src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml diff --git a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj index dc2a075..e6db671 100644 --- a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj +++ b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj @@ -26,8 +26,9 @@ - - + + + diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml new file mode 100644 index 0000000..623a90c --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml @@ -0,0 +1,68 @@ +@page "/health-ui" +@model DigitalData.MessagingService.API.Pages.HealthDashboardModel +@{ + ViewData["Title"] = "Health Dashboard"; + Layout = "_Layout"; +} + + + +

Health Dashboard

+ +
+ @(Model.OverallStatus == Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy ? "?" : "?") + @Model.OverallStatus +
+ +

+ Zuletzt geprüft: @Model.CheckedAt.ToString("yyyy-MM-dd HH:mm:ss") UTC +  ·  Aktualisieren +  ·  JSON +

+ +
+ @foreach (var entry in Model.Entries) + { +
+
+ @entry.Name + @entry.Status +
+ @if (!string.IsNullOrWhiteSpace(entry.Description)) + { +
@entry.Description
+ } + @if (entry.Tags.Any()) + { +
+ @foreach (var tag in entry.Tags) + { + @tag + } +
+ } +
Dauer: @entry.Duration.TotalMilliseconds.ToString("0")ms
+
+ } +
diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs new file mode 100644 index 0000000..530f698 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace DigitalData.MessagingService.API.Pages; + +public class HealthDashboardModel(HealthCheckService healthCheckService) : PageModel +{ + public HealthStatus OverallStatus { get; private set; } + public DateTime CheckedAt { get; private set; } + public IReadOnlyList Entries { get; private set; } = []; + + public async Task OnGetAsync() + { + var report = await healthCheckService.CheckHealthAsync(); + CheckedAt = DateTime.UtcNow; + OverallStatus = report.Status; + Entries = report.Entries + .Select(e => new HealthEntry( + Name: e.Key, + Status: e.Value.Status, + Description: e.Value.Description, + Duration: e.Value.Duration, + Tags: e.Value.Tags)) + .OrderBy(e => e.Status) + .ToList(); + } + + public record HealthEntry( + string Name, + HealthStatus Status, + string? Description, + TimeSpan Duration, + IEnumerable Tags); +} diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml new file mode 100644 index 0000000..cbba227 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml @@ -0,0 +1,28 @@ + + + + + + @ViewData["Title"] — DigitalData MessagingService + + + +
+ DigitalData MessagingService + Health + Logs + API +
+
+ @RenderBody() +
+ + diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..b697ff5 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using DigitalData.MessagingService.API.Pages +@namespace DigitalData.MessagingService.API.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/presentation/DigitalData.MessagingService.API/Program.cs b/src/presentation/DigitalData.MessagingService.API/Program.cs index e1b85b3..cf43e75 100644 --- a/src/presentation/DigitalData.MessagingService.API/Program.cs +++ b/src/presentation/DigitalData.MessagingService.API/Program.cs @@ -1,6 +1,10 @@ using DigitalData.MessagingService.API.Middleware; using DigitalData.MessagingService.Application; using DigitalData.MessagingService.Infrastructure; +using DigitalData.MessagingService.Infrastructure.Services.Background; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Serilog; using Serilog.Ui.Core.Extensions; using Serilog.Ui.SqliteDataProvider.Extensions; @@ -66,7 +70,14 @@ try // Register Infrastructure layer (RabbitMQ, Repositories, etc.) builder.Services.AddInfrastructure(builder.Configuration); + // Health checks — each check corresponds to a singleton registered in the Infrastructure layer + builder.Services.AddHealthChecks() + .AddCheck("rabbitmq-consumers", tags: ["rabbitmq", "ready"]) + .AddCheck("rabbitmq-connection", tags: ["rabbitmq", "live"]) + .AddCheck("email-sync-worker", tags: ["workers", "live"]); + builder.Services.AddControllers(); + builder.Services.AddRazorPages(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => @@ -95,6 +106,7 @@ try ## WeiterfĂĽhrende Links - [Serilog Log-Viewer ↗](/serilog-ui) + - [Health Status ↗](/health-ui) """ }); }); @@ -155,6 +167,22 @@ try app.MapControllers(); + // Health check endpoint — raw JSON, used by HealthChecks UI and monitoring tools + // Returns HTTP 503 if any check tagged 'ready' is Unhealthy + app.MapHealthChecks("/health", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, + ResultStatusCodes = + { + [HealthStatus.Healthy] = StatusCodes.Status200OK, + [HealthStatus.Degraded] = StatusCodes.Status200OK, + [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable, + } + }); + + app.MapRazorPages(); + Log.Information("MessagingService API started successfully"); app.Run();