Every outbound HTTP call to another service will eventually time out, get rate-limited, or fail transiently. The question isn't whether that happens — it's whether your app has a plan for it. Microsoft.Extensions.Http.Resilience, built on Polly, is the current standard way to add that plan to HttpClientFactory in .NET.
AddStandardResilienceHandler()gives you retry, circuit breaker, timeout, and concurrency limiting on anHttpClientin one call, with sensible defaults.- Build a custom pipeline when the standard handler's defaults don't fit — different retry counts for idempotent vs non-idempotent calls, for example.
- Split health checks into liveness (is the process alive) and readiness (is it able to serve traffic right now) — orchestrators use them differently.
- Outbound resilience (retries, circuit breakers) and inbound rate limiting are two sides of the same problem — protecting yourself from downstream failures, and protecting your own service from being overwhelmed.
The standard resilience handler
builder.Services.AddHttpClient("inventory-api", client =>
{
client.BaseAddress = new Uri(builder.Configuration["InventoryApi:BaseUrl"]!);
})
.AddStandardResilienceHandler();
One line adds a pipeline with retry (with jitter), a circuit breaker, a per-attempt timeout, an overall request timeout, and a concurrency limiter — tuned defaults that cover most outbound calls without hand-configuring each strategy.
Customizing the pipeline
When the defaults don't fit — for example, you only want retries on GET requests, not on a POST that isn't idempotent — configure the pipeline explicitly.
builder.Services.AddHttpClient("inventory-api")
.AddResilienceHandler("inventory-pipeline", pipeline =>
{
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = args => ValueTask.FromResult(
args.Outcome.Result?.RequestMessage?.Method == HttpMethod.Get &&
(args.Outcome.Result?.StatusCode is HttpStatusCode.ServiceUnavailable
or HttpStatusCode.GatewayTimeout))
});
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 10,
});
pipeline.AddTimeout(TimeSpan.FromSeconds(5));
});
Liveness vs readiness health checks
Liveness answers "is the process healthy enough to keep running" — a failing liveness check tells the orchestrator to restart the container. Readiness answers "should traffic be routed here right now" — a failing readiness check pulls the instance out of rotation without restarting it. Mixing the two into one /health endpoint causes orchestrators to restart healthy instances just because a downstream dependency is temporarily down.
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddNpgSql(connectionString, tags: ["ready"])
.AddUrlGroup(new Uri("https://inventory-api/health"), name: "inventory-api", tags: ["ready"]);
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = c => c.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") });
Liveness should only check things that indicate the process itself is broken — deadlocked, out of memory. Readiness should check the dependencies that are actually required to serve a request — database connectivity, critical downstream APIs.
AddUrlGroup for another service) make your readiness endpoint only as available as the least reliable thing you depend on. If a non-critical downstream dependency going down shouldn't take you out of rotation, don't tag its check as ready — log it and degrade gracefully instead.The inbound counterpart: rate limiting
Resilience handlers protect you from downstream failures. ASP.NET Core's built-in rate limiting middleware protects you from being overwhelmed by your own callers — a fixed window, sliding window, token bucket, or concurrency limiter applied to inbound requests, configured once in Program.cs with AddRateLimiter and applied per-endpoint or globally.