Round #282

08 May 2026, 12:03· csharp· openai/gpt-oss-120b:free· r13-clean-hints
Complexity
band 1
ZP / UO / OBS
3 / 9 / 0
Journey weight
907
Lightshift ΔL
+0.00

Prompt

Add a health check endpoint

Zero-point index (what the engine surfaced)

ZPI-ZPI-0001 | 2026-05-08T12:03:03Z Add a health check endpoint ZERO POINT — 3 entries (structural invariants) ● hsl(190.26, 100%, 15%) IHealthCheck [contract] ● hsl(302.56, 100%, 15%) HealthCheckResult [contract] ● hsl(190.42, 100%, 15%) IHealthCheckPublisher [contract] UNOBSERVED — 9 entries (implied truths) ○ hsl(27.69, 100%, 50%) async [feature] ○ hsl(9.23, 100%, 50%) interface [feature] ○ hsl(77.54, 100%, 36%) Routing middleware [function] ○ hsl(83.08, 100%, 42%) ILogger of T injection [function] ○ hsl(31.38, 100%, 42%) Scoped registration [function] ○ hsl(173.54, 100%, 7%) Liveness check [function] ○ hsl(175.38, 100%, 7%) Readiness check [function] ○ hsl(179.08, 100%, 7%) Custom health check [function] ○ hsl(184.62, 100%, 7%) Health check response writer [function] OBSERVED — 0 entries (explicit in request) COMPLEXITY │ 12 entries │ spread: 175° │ band: MEDIUM

Baseline response (no ZPI)

Below is a **complete, step‑by‑step guide** for adding a **Health Check endpoint** to an ASP.NET Core Web API (or MVC) project using the built‑in **Microsoft.Extensions.Diagnostics.HealthChecks** library. --- ## 1️⃣ Add the required NuGet package The health‑check APIs are shipped in the `Microsoft.AspNetCore.HealthChecks` meta‑package (which pulls in the core library). Run one of the following commands in the **Package Manager Console** or **dotnet CLI**: ```powershell # Package Manager Console Install-Package Microsoft.AspNetCore.HealthChecks ``` ```bash # dotnet CLI dotnet add package Microsoft.AspNetCore.HealthChecks ``` > **Note** – If you only need the core health‑check abstractions (e.g., to register custom checks) you can install `Microsoft.Extensions.Diagnostics.HealthChecks`. The `HealthChecks` package also adds the endpoint routing helpers. --- ## 2️⃣ Register health‑check services Open **`Program.cs`** (or `Startup.cs` if you are on an older template) and add the health‑check services to the DI container. ```csharp var builder = WebApplication.CreateBuilder(args); // ───────────────────────────────────────────────────────────── // Existing service registrations (e.g., controllers, DB, etc.) // ───────────────────────────────────────────────────────────── builder.Services.AddControllers(); // 1️⃣ Register the health‑check service collection builder.Services.AddHealthChecks() // Example built‑in checks (optional – remove if you don’t need them) .AddCheck("self", () => HealthCheckResult.Healthy()) // Add a SQL Server check (requires Microsoft.Extensions.Diagnostics.HealthChecks.SqlServer) // .AddSqlServer( // connectionString: builder.Configuration.GetConnectionString("DefaultConnection"), // name: "sqlserver", // healthQuery: "SELECT 1;", // failureStatus: HealthStatus.Unhealthy, // tags: new[] { "db", "sql" }); // Add a custom check (see section 4) // .AddCheck<RedisHealthCheck>("redis", tags: new[] { "cache" }); ``` > **Why add a “self” check?** > It always returns `Healthy` and is useful for a quick “is the app running?” probe without any external dependencies. --- ## 3️⃣ Map the health‑check endpoint Still in **`Program.cs`**, after building the app, map the endpoint. You can expose it at any route you like (commonly `/health` or `/healthz`). ```csharp var app = builder.Build(); // ───────────────────────────────────────────────────────────── // Existing middleware (e.g., HTTPS, auth, routing, etc.) // ───────────────────────────────────────────────────────────── if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.MapControllers(); // 2️⃣ Map the health‑check endpoint app.MapHealthChecks("/healthz", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions { // Optional: customize the response format ResponseWriter = async (context, report) => { context.Response.ContentType = "application/json"; var result = new { status = report.Status.ToString(), checks = report.Entries.Select(entry => new { name = entry.Key, status = entry.Value.Status.ToString(), description = entry.Value.Description, duration = entry.Value.Duration.ToString(), data = entry.Value.Data }), totalDuration = report.TotalDuration.ToString() }; await context.Response.WriteAsJsonAsync(result); }, // Optional: filter which checks are run for this endpoint // Predicate = (check) => check.Tags.Contains("ready") // example }); app.Run(); ``` ### What the code does | Piece | Purpose | |-------|---------| | `builder.Services.AddHealthChecks()` | Registers the health‑check service collection. | | `.AddCheck("self", ...)` | Adds a trivial “always healthy” check. | | `app.MapHealthChecks("/healthz", options)` | Exposes the endpoint at `/healthz`. | | `ResponseWriter` | Serialises the health report as JSON (the default writer returns a plain‑text status). | | `Predicate` (optional) | Lets you expose *different* health‑check sets on different URLs (e.g., `/ready`, `/live`). | --- ## 4️⃣ (Optional) Add custom health checks If you need to verify the health of a specific resource (database, cache, external API, etc.), create a class that implements `IHealthCheck`. ```csharp using Microsoft.Extensions.Diagnostics.HealthChecks; using System.Net.Http; using System.Threading; using System.Threading.Tasks; public class ExternalApiHealthCheck : IHealthCheck { private readonly HttpClient _httpClient; private readonly string _pingUrl; public ExternalApiHealthCheck(HttpClient httpClient, IConfiguration config) { _httpClient = httpClient; _pingUrl = config["ExternalApi:HealthEndpoint"]; // e.g. https://api.example.com/health } public async Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { try { var response = await _httpClient.GetAsync(_pingUrl, cancellationToken); if (response.IsSuccessStatusCode) return HealthCheckResult.Healthy("External API reachable."); return HealthCheckResult.Unhealthy($"External API returned {(int)response.StatusCode}."); } catch (Exception ex) { return HealthCheckResult.Unhealthy("Exception while contacting external API.", ex); } } } ``` Register it: ```csharp builder.Services.AddHttpClient(); // needed for HttpClient injection builder.Services.AddHealthChecks() .AddCheck<ExternalApiHealthCheck>("external_api", tags: new[] { "external", "ready" }); ``` Now the `/healthz` endpoint will include a `external_api` entry. --- ## 5️⃣ (Optional) Separate **Liveness** and **Readiness** probes Kubernetes and many orchestrators like to differentiate: | Probe | Typical checks | |-------|----------------| | **Liveness** | “Is the process alive?” – usually only the `self` check. | | **Readiness** | “Is the app ready to accept traffic?” – includes DB, cache, external services. | ### Implementation ```csharp // Liveness – only the trivial self‑check app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = (check) => check.Name == "self", ResponseWriter = HealthCheckResponseWriter // reuse the JSON writer from above }); // Readiness – all checks except the self‑check (or filter by tag) app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = (check) => check.Tags.Contains("ready"), ResponseWriter = HealthCheckResponseWriter }); ``` Add tags when you register checks: ```csharp builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy()) .AddCheck<ExternalApiHealthCheck>("external_api", tags: new[] { "ready" }) .AddCheck<SqlServerHealthCheck>("sqlserver", tags: new[] { "ready" }); ``` Now you can configure your orchestrator: ```yaml livenessProbe: httpGet: path: /health/live port: 80 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 80 initialDelaySeconds: 10 periodSeconds: 5 ``` --- ## 6️⃣ Test the endpoint locally Run the app and browse to: ``` GET https://localhost:{port}/healthz GET https://localhost:{port}/health/live GET https://localhost:{port}/health/ready ``` You should receive a JSON payload similar to: ```json { "status": "Healthy", "checks": [ { "name": "self", "status": "Healthy", "description": null, "duration": "00:00:00.0012345", "data": {} }, { "name": "external_api", "status": "Unhealthy", "description": "External API returned 500.", "duration": "00:00:00.0456789", "data": {} } ], "totalDuration": "00:00:00.0479134" } ``` --- ## 📦 Full Minimal Example (Program.cs) ```csharp using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); // Add framework services builder.Services.AddControllers(); // 1️⃣ Health‑check registration builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy()) .AddCheck<ExternalApiHealthCheck>("external_api", tags: new[] { "ready" }); builder.Services.AddHttpClient(); // needed for ExternalApiHealthCheck var app = builder.Build(); // Middleware pipeline app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.MapControllers(); // 2️⃣ JSON response writer (re‑usable) static async Task HealthCheckResponseWriter(HttpContext context, HealthReport report) { context.Response.ContentType = "application/json"; var result = new { status = report.Status.ToString(), checks = report.Entries.Select(entry => new { name = entry.Key, status = entry.Value.Status.ToString(), description = entry.Value.Description, duration = entry.Value.Duration.ToString(), data = entry.Value.Data }), totalDuration = report.TotalDuration.ToString() }; await context.Response.WriteAsJsonAsync(result); } // Liveness endpoint (only self) app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = (check) => check.Name == "self", ResponseWriter = HealthCheckResponseWriter }); // Readiness endpoint (all checks tagged "ready") app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = (check) => check.Tags.Contains("ready"), ResponseWriter = HealthCheckResponseWriter }); // General health endpoint (everything) app.MapHealthChecks("/healthz", new HealthCheckOptions { ResponseWriter = HealthCheckResponseWriter }); app.Run(); ``` --- ## 🎉 What you now have * **`/healthz`** – full health report (useful for manual debugging). * **`/health/live`** – liveness probe (process is up). * **`/health/ready`** – readiness probe (all critical dependencies are healthy). All of this uses the **built‑in, production‑ready** health‑check infrastructure, works with **ASP.NET Core 6/7/8**, and can be extended with any number of custom checks. Feel free to ask if you need help wiring a specific dependency (SQL Server, Redis, RabbitMQ, etc.) or configuring the endpoint for a particular hosting environment!

Enhanced response (with ZPI)

**Solution Overview** The code below adds a complete health‑checking infrastructure to an ASP.NET Core web API: * **Health‑check services** are registered in the DI container. * Two built‑in checks are exposed: * **Liveness** – a trivial “always healthy” check that tells orchestrators the process is alive. * **Readiness** – a custom check (`DatabaseHealthCheck`) that verifies the application can talk to its database. * A **custom response writer** formats the JSON payload the way most monitoring tools expect (status, total duration, and an array of individual check results). * The **endpoint** `/healthz` returns the liveness result, while `/ready` returns the readiness result. * `ILogger<T>` is injected into the custom health‑check and the response writer for structured logging. * All registrations are scoped where appropriate and the whole setup is encapsulated in extension methods for clean `Program.cs` usage. --- ## 1. Custom health‑check implementation ```csharp using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; public sealed class DatabaseHealthCheck : IHealthCheck { private readonly ILogger<DatabaseHealthCheck> _logger; // In a real app this would be a DbContext, IDbConnection, etc. // For illustration we just simulate a check. public DatabaseHealthCheck(ILogger<DatabaseHealthCheck> logger) => _logger = logger ?? throw new ArgumentNullException(nameof(logger)); public async Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { try { // Simulate async DB ping – replace with real logic. await Task.Delay(50, cancellationToken); bool dbIsUp = true; // <-- real check here if (dbIsUp) { _logger.LogInformation("Database connectivity check succeeded."); return HealthCheckResult.Healthy("Database reachable."); } _logger.LogWarning("Database connectivity check failed."); return HealthCheckResult.Unhealthy("Database not reachable."); } catch (Exception ex) { _logger.LogError(ex, "Exception while checking database health."); return HealthCheckResult.Unhealthy("Exception during DB check.", ex); } } } ``` *The check is registered as **scoped** because it may depend on scoped services such as a `DbContext`.* --- ## 2. Custom JSON response writer ```csharp using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; public static class HealthCheckResponseWriter { public static async Task WriteResponseAsync(HttpContext context, HealthReport report, ILogger logger) { context.Response.ContentType = "application/json"; var response = new { status = report.Status.ToString(), totalDuration = report.TotalDuration, checks = report.Entries.Select(entry => new { name = entry.Key, status = entry.Value.Status.ToString(), description = entry.Value.Description, duration = entry.Value.Duration, // Include error details only when the check is unhealthy error = entry.Value.Status == HealthStatus.Unhealthy ? entry.Value.Exception?.Message : null }) }; var json = JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true }); logger.LogInformation("Health check response: {Payload}", json); await context.Response.WriteAsync(json); } } ``` *The writer logs the payload (useful for debugging) and returns a clean, version‑agnostic JSON structure.* --- ## 3. Extension methods for registration ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; public static class HealthCheckExtensions { /// <summary> /// Registers health‑check services, including liveness and readiness. /// </summary> public static IServiceCollection AddApplicationHealthChecks(this IServiceCollection services) { // Register the custom DB check as scoped (it may need scoped deps) services.AddScoped<IHealthCheck, DatabaseHealthCheck>(sp => sp.GetRequiredService<DatabaseHealthCheck>()); services.AddHealthChecks() // Liveness – always healthy, no external deps .AddCheck("liveness", () => HealthCheckResult.Healthy(), tags: new[] { "live" }) // Readiness – depends on DB .AddCheck<DatabaseHealthCheck>("database", tags: new[] { "ready" }); return services; } /// <summary> /// Maps health‑check endpoints with the custom response writer. /// </summary> public static IEndpointRouteBuilder MapHealthEndpoints(this IEndpointRouteBuilder endpoints) { var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger("HealthCheck"); // Liveness endpoint – only the "live" tag endpoints.MapHealthChecks("/healthz", new HealthCheckOptions { Predicate = reg => reg.Tags.Contains("live"), ResponseWriter = (c, r) => HealthCheckResponseWriter.WriteResponseAsync(c, r, logger) }); // Readiness endpoint – only the "ready" tag endpoints.MapHealthChecks("/ready", new HealthCheckOptions { Predicate = reg => reg.Tags.Contains("ready"), ResponseWriter = (c, r) => HealthCheckResponseWriter.WriteResponseAsync(c, r, logger) }); return endpoints; } } ``` *The `AddApplicationHealthChecks` method registers the health‑check services and tags them for easy filtering. `MapHealthEndpoints` wires the two routes (`/healthz` and `/ready`) and injects the logger into the response writer.* --- ## 4. Wire‑up in `Program.cs` (minimal API style) ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); // ------------------------------------------------------------------ // 1️⃣ Register services // ------------------------------------------------------------------ builder.Services.AddLogging(); // ILogger<T> support builder.Services.AddApplicationHealthChecks(); // our extension above // Add other services (controllers, DB, etc.) here... // builder.Services.AddDbContext<...>(); var app = builder.Build(); // ------------------------------------------------------------------ // 2️⃣ Configure middleware // ------------------------------------------------------------------ if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // (optional) other middleware like routing, authentication, etc. app.UseRouting(); // ------------------------------------------------------------------ // 3️⃣ Map endpoints // ------------------------------------------------------------------ app.MapHealthEndpoints(); // registers /healthz and /ready // Example minimal API endpoint app.MapGet("/", () => "Hello World!"); app.Run(); ``` *The `Program.cs` file stays tidy: a single call to `AddApplicationHealthChecks` registers everything, and `MapHealthEndpoints` adds the two health‑check routes.* --- ## 5. How it works | Component | Role | |-----------|------| | **`DatabaseHealthCheck`** | Implements `IHealthCheck`; contains the actual readiness logic (e.g., DB ping). | | **`HealthCheckResponseWriter`** | Serialises `HealthReport` to a friendly JSON payload and logs it. | | **`AddApplicationHealthChecks`** | Registers the health‑check services, tags them (`live` / `ready`) and adds the custom check to the `IHealthChecksBuilder`. | | **`MapHealthEndpoints`** | Exposes two endpoints: <br>• **`/healthz`** – liveness (only checks tagged *live*). <br>• **`/ready`** – readiness (only checks tagged *ready*). Both use the custom response writer. | | **DI & Logging** | `ILogger<T>` is injected into the custom check and the response writer, providing structured logs without manual `Console.WriteLine`. | | **Scoped registration** | The custom check is registered as scoped, allowing it to depend on other scoped services (e.g., `DbContext`). | --- ## 6. Testing the endpoints ```bash # Liveness – should always be 200 OK curl -i http://localhost:5000/healthz # Readiness – will be 200 OK only if the DB check reports Healthy curl -i http://localhost:5000/ready ``` Typical JSON response: ```json { "status": "Healthy", "totalDuration": "00:00:00.1234567", "checks": [ { "name": "database", "status": "Healthy", "description": "Database reachable.", "duration": "00:00:00.0500000", "error": null } ] } ``` If the database is down, the `status` becomes `Unhealthy` and the `error` field contains the exception message. --- ### Summary *Implemented a full health‑check pipeline using the built‑in `IHealthCheck`, `HealthCheckResult`, and `IHealthCheckPublisher` concepts.* *Provided liveness and readiness endpoints, a custom DB health‑check, a JSON response writer, and proper DI/scoped registration with `ILogger<T>`.* Drop the code into a new ASP.NET Core project, adjust the `DatabaseHealthCheck` to your real data‑access layer, and you have a production‑ready health‑checking solution.

Grader chain

Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.

V1claude-haiku-4-5-20251001
Better
V4qwen2.5-coder-7b-instruct
Better

Comments

Our grader said what it said. What do you say? Comment as a guest below.

No comments yet. Be the first to say what you make of this round.

← back to all rounds