ASP.NET Core cookbook · Intermediate
Call external APIs with typed HttpClients and resilience
Outbound calls exhaust sockets, have no timeouts, and fail on transient errors.
Approach
Register a typed client with AddHttpClient (pooled handlers, DNS refresh), configure its base address, and add
the standard resilience pipeline (retries with jitter, circuit breaker, timeouts).
Solution
using System.Net.Http.Json;
using Microsoft.Extensions.Http.Resilience;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<ShippingClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Shipping:BaseUrl"] ?? "https://shipping.example.com/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(2);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(8);
});
var app = builder.Build();
app.MapGet("/api/shipping/{country}", async (string country, ShippingClient shipping, CancellationToken ct) =>
await shipping.GetQuoteAsync(country, ct) is { } quote ? Results.Ok(quote) : Results.StatusCode(503));
app.Run();
public record Quote(string Country, int PriceCents);
public class ShippingClient(HttpClient http, ILogger<ShippingClient> logger)
{
public async Task<Quote?> GetQuoteAsync(string country, CancellationToken ct)
{
try
{
return await http.GetFromJsonAsync<Quote>($"quotes/{Uri.EscapeDataString(country)}", ct);
}
catch (HttpRequestException ex)
{
logger.LogWarning(ex, "Shipping quote failed for {Country}", country);
return null;
}
}
}
Pitfalls
new HttpClient()per request, exhausting sockets; or one static instance that never sees DNS changes.- Retrying POSTs that aren't idempotent; disable retries for unsafe methods or send idempotency keys.
- A total timeout shorter than retries need, or no total timeout at all.
Variations
- Build custom pipelines with
AddResilienceHandler("name", pipeline => ...)(Polly v8). - Add a hedging handler for latency-sensitive reads across replicas.
Further reading: HTTP resilience
Reference: https://learn.microsoft.com/en-us/dotnet/core/resilience/http-resilience
More ASP.NET Core recipes
- Accept file uploads with size and type limits
- Add OpenTelemetry traces and metrics
- Apply EF Core migrations safely during deployment
- Bind and validate options at startup
- Cache responses with output caching and evict by tag
- Configure CORS for a browser front end
- Expose liveness and readiness health checks
- Generate OpenAPI documents with Microsoft.AspNetCore.OpenApi