// Program.cs
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health");
// A simple GET /health request returns "Healthy" with 200 OK if no checks fail
Topics
36
API Documentation with Swagger/OpenAPI
API Versioning
Authentication
Authorization
Background Tasks & Hosted Services
Blazor Integration with ASP.NET Core
Caching
Configuration & Options Pattern
Content Negotiation & Output Formatters
Controllers vs Minimal APIs
CORS (Cross-Origin Resource Sharing)
Dependency Injection
Endpoint Metadata, Route Constraints & Templates
Error Handling
File Uploads & Streaming Large Files
Filters
gRPC Services
Health Checks
Hosting Models: Kestrel, IIS & Reverse Proxies
HTTPS, Certificates & Transport Security
Localization & Globalization
Logging
Model Binding & Validation
MVC Views, Razor Syntax & Tag Helpers
Output Caching & Response Caching
Rate Limiting
Razor Pages
Request Pipeline & Middleware
Response Compression & Caching Headers
Routing
Security Headers, Antiforgery & CSRF Protection
Sessions, Cookies & TempData
SignalR & Real-Time Communication
Static Files, wwwroot & Content Delivery
Testing ASP.NET Core Applications
WebSockets
Health Checks
15 questions found
The Health Checks middleware (Microsoft.AspNetCore.Diagnostics.HealthChecks) provides a standardized way for an application to report its own operational health status, exposed via an HTTP endpoint (typically /health) that returns Healthy, Degraded, or Unhealthy -- this lets external systems (load balancers, container orchestrators like Kubernetes, monitoring dashboards) programmatically determine whether an instance is functioning correctly and should continue receiving traffic, without needing custom application-specific monitoring logic.
Real-world example
A Kubernetes deployment configures a liveness probe pointing at an app's /health endpoint, automatically restarting any pod instance that starts reporting Unhealthy, without requiring manual monitoring intervention.
Hosting Models: Kestrel
IIS & Reverse Proxies;Background Tasks & Hosted Services
How do you implement a custom health check by implementing IHealthCheck, such as checking database connectivity?
IntermediateImplement IHealthCheck's single CheckHealthAsync method, returning HealthCheckResult.Healthy(), .Degraded(), or .Unhealthy() (each optionally with a description and data dictionary) based on whatever logic determines the dependency's status -- register it via AddHealthChecks().AddCheck<T>("name") or the inline AddCheck("name", () => ...) overload for simple synchronous checks.
public class DatabaseHealthCheck(AppDbContext db) : IHealthCheck {
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) {
try {
await db.Database.CanConnectAsync(ct);
return HealthCheckResult.Healthy("Database connection OK");
} catch (Exception ex) {
return HealthCheckResult.Unhealthy("Database connection failed", ex);
}
}
}
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database");
Real-world example
An e-commerce API registers a custom DatabaseHealthCheck verifying it can actually connect to the database (not just that the connection string is configured), causing the /health endpoint to report Unhealthy during a database outage even while the app process itself is still running fine.
Configuration & Options Pattern;Dependency Injection
How do you differentiate liveness and readiness health checks using tags, and why does Kubernetes treat these two probe types differently?
AdvancedLiveness checks answer 'is the process alive and not deadlocked' (a failure means the container should be restarted), while readiness checks answer 'is this instance ready to receive traffic' (a failure means temporarily remove it from the load balancer's rotation without restarting it, useful during startup or brief downstream dependency issues) -- implemented by tagging individual health checks (AddCheck(..., tags: new[] { "ready" })) and configuring separate MapHealthChecks endpoints filtered by tag via the Predicate option, matched to Kubernetes' separate livenessProbe and readinessProbe configuration.
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database", tags: new[] { "ready" })
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" });
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = c => c.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") });
Real-world example
A Kubernetes deployment configures livenessProbe against /health/live (checking only that the process itself is responsive, restarting on failure) and readinessProbe against /health/ready (checking database connectivity, temporarily pulling the pod from service rotation on failure without restarting it), correctly distinguishing 'crash and restart me' from 'temporarily stop sending me traffic.'
Hosting Models: Kestrel
IIS & Reverse Proxies;Background Tasks & Hosted Services
How do you customize the JSON response format of the health check endpoint, since the default output is just plain text status?
IntermediateThe default MapHealthChecks() endpoint returns only plain text ("Healthy", "Unhealthy"), but you can supply a custom ResponseWriter delegate in HealthCheckOptions to produce a structured JSON response including each individual check's name, status, duration, and description -- valuable for monitoring dashboards or debugging which specific dependency is causing an overall Unhealthy status.
app.MapHealthChecks("/health", new HealthCheckOptions {
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(), duration = e.Value.Duration.TotalMilliseconds })
});
await context.Response.WriteAsync(result);
}
});
Real-world example
A monitoring dashboard consumes a custom JSON-formatted /health response showing each individual dependency's status and response time, letting operators immediately identify that it's specifically the Redis cache check failing rather than just knowing the overall status is Unhealthy.
API Documentation with Swagger/OpenAPI;Logging
How would you implement a health check that reports Degraded (rather than a binary Healthy/Unhealthy) based on a performance threshold, like elevated database query latency?
AdvancedMeasure the relevant metric (e.g., time a test query takes) inside CheckHealthAsync and compare it against defined thresholds, returning HealthCheckResult.Healthy() below a normal threshold, .Degraded() when elevated but still functional, and .Unhealthy() when it exceeds a failure threshold entirely -- letting monitoring/alerting systems distinguish 'working but slow, investigate soon' from 'completely broken, page someone now,' since these two situations warrant very different responses.
public class DatabaseLatencyHealthCheck(AppDbContext db) : IHealthCheck {
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) {
var sw = Stopwatch.StartNew();
await db.Database.ExecuteSqlRawAsync("SELECT 1", ct);
sw.Stop();
return sw.ElapsedMilliseconds switch {
< 100 => HealthCheckResult.Healthy($"{sw.ElapsedMilliseconds}ms"),
< 1000 => HealthCheckResult.Degraded($"Slow: {sw.ElapsedMilliseconds}ms"),
_ => HealthCheckResult.Unhealthy($"Very slow: {sw.ElapsedMilliseconds}ms")
};
}
}
Real-world example
An operations team's alerting distinguishes a Degraded database health status (query latency between 100ms-1s, triggering a low-priority Slack notification) from Unhealthy (over 1s, triggering an immediate on-call page), avoiding alert fatigue from paging on every minor latency blip while still catching genuine outages promptly.
Diagnostics & Performance;Logging
How do you add pre-built health checks for common external dependencies like SQL Server, Redis, or a URL endpoint, using the community AspNetCore.Diagnostics.HealthChecks packages?
IntermediateThe community-maintained AspNetCore.Diagnostics.HealthChecks repository provides numerous ready-made health check packages (AspNetCore.HealthChecks.SqlServer, .Redis, .Uri, etc.) for common dependencies, avoiding the need to hand-write IHealthCheck implementations for standard infrastructure -- each is registered via a fluent extension method on the IHealthChecksBuilder, typically just needing a connection string or URL.
// After: dotnet add package AspNetCore.HealthChecks.SqlServer
// dotnet add package AspNetCore.HealthChecks.Redis
builder.Services.AddHealthChecks()
.AddSqlServer(builder.Configuration.GetConnectionString("Default"), name: "sql-server")
.AddRedis(builder.Configuration.GetConnectionString("Redis"), name: "redis")
.AddUrlGroup(new Uri("https://api.partner.com/health"), name: "partner-api");
Real-world example
A team adds SQL Server, Redis, and an external partner API health check to their /health endpoint entirely through community NuGet packages and one-line registrations, avoiding weeks of effort hand-writing and testing custom connectivity-checking logic for each dependency type.
Configuration & Options Pattern;Caching
How would you integrate health check results with the AspNetCore.HealthChecks.UI package to get a persistent, historical dashboard view of health status over time?
AdvancedThe HealthChecks.UI package adds a separate web dashboard (typically at /healthchecks-ui) that periodically polls one or more configured health check endpoints, storing historical results and displaying status trends over time (not just the current instantaneous status), plus configurable webhook notifications when status changes -- valuable for spotting intermittent or recurring health issues that a single point-in-time check wouldn't reveal.
// Program.cs
builder.Services.AddHealthChecksUI(options => {
options.AddHealthCheckEndpoint("API", "/health");
}).AddInMemoryStorage();
var app = builder.Build();
app.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => true, ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse });
app.MapHealthChecksUI(options => options.UIPath = "/healthchecks-ui");
Real-world example
An operations team deploys the HealthChecks.UI dashboard polling their API's /health endpoint every 30 seconds, quickly noticing a pattern where the Redis check intermittently degrades every night during a scheduled backup job -- a pattern invisible from a single current-status check alone.
Logging;Configuration & Options Pattern
How do health checks interact with dependency injection scoping, particularly for checks that need a scoped service like a DbContext?
IntermediateSince health checks execute outside the normal per-request DI scope (they run on-demand when the health endpoint is hit, or via a background polling service), the health check middleware itself creates an appropriate DI scope when invoking each IHealthCheck implementation, so a health check class can safely take a constructor-injected scoped service like a DbContext just like it would in a controller -- but you should avoid manually caching or holding onto scoped services in a health check registered with a longer lifetime, since that could lead to using a disposed instance.
public class OrderDbHealthCheck(OrderDbContext db) : IHealthCheck { // scoped DbContext injected safely
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) {
return await db.Database.CanConnectAsync(ct) ? HealthCheckResult.Healthy() : HealthCheckResult.Unhealthy();
}
}
// Registration -- the health check itself is registered, DI scope handled automatically per invocation
builder.Services.AddHealthChecks().AddCheck<OrderDbHealthCheck>("orders-db");
Real-world example
A health check class safely takes a constructor-injected scoped DbContext, relying on the health check middleware's automatic per-invocation DI scope creation, rather than needing to manually manage IServiceScopeFactory and scope lifetime themselves.
Dependency Injection;Background Tasks & Hosted Services
How would you set a per-check timeout to prevent one slow or hanging dependency check from blocking the entire /health endpoint response indefinitely?
AdvancedWrap the health check's actual work with a CancellationTokenSource-based timeout (either handled manually inside CheckHealthAsync using the passed CancellationToken combined with a timeout token, or via HealthCheckOptions.Timeout at the overall endpoint level) so a single unresponsive dependency (like a database under heavy load) times out and reports Unhealthy for that specific check rather than causing the entire /health endpoint request to hang indefinitely waiting for a response that may never come.
public class SlowDependencyHealthCheck : IHealthCheck {
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) {
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(3));
try {
await _dependency.PingAsync(timeoutCts.Token);
return HealthCheckResult.Healthy();
} catch (OperationCanceledException) {
return HealthCheckResult.Unhealthy("Dependency check timed out after 3s");
}
}
}
Real-world example
A health check for a flaky third-party API is given an explicit 3-second timeout, ensuring that when the third-party API hangs for 30+ seconds, the overall /health endpoint still responds promptly reporting that one specific check as Unhealthy, rather than the entire endpoint (and any load balancer probing it) hanging indefinitely.
Diagnostics & Performance;HttpClient & Resilience (Polly)
Why should the detailed /health endpoint (with full dependency status breakdown) generally not be exposed publicly on the internet, and how do you restrict access to it?
IntermediateA detailed health check response can leak sensitive infrastructure information (which database provider is used, internal service names, version numbers, error messages potentially revealing internal architecture) to anyone who can reach the endpoint -- best practice restricts detailed health endpoints to internal networks only (via network-level restrictions, a reverse proxy rule, or [Authorize] combined with a policy requiring an internal-only claim), while optionally exposing a minimal, information-free public endpoint (just 200 OK / 503) for basic external uptime monitoring.
// Program.cs -- separate detailed (internal) and minimal (public) health endpoints
app.MapHealthChecks("/health/detailed", new HealthCheckOptions {
ResponseWriter = WriteDetailedJson
}).RequireAuthorization("InternalOnly");
app.MapHealthChecks("/health", new HealthCheckOptions {
ResponseWriter = (context, report) => context.Response.WriteAsync(report.Status.ToString())
}); // minimal, publicly safe
Real-world example
A security review flags that an API's detailed /health endpoint was publicly exposing its exact SQL Server version, Redis hostname, and internal service names, leading the team to restrict the detailed endpoint to internal network access only while keeping a minimal public endpoint for basic uptime monitoring.
Security Headers
Antiforgery & CSRF Protection;Authorization
Showing 1–10 of 15