.NET 8+ bringt eine integrierte Resilience-Pipeline für HttpClient mit, die typische Fehlerfälle bei HTTP-Aufrufen sauber abfedert – ganz ohne selbstgeschriebene Retry-Schleifen oder externe Libraries.
Besonders relevant sind dabei temporäre Fehler, bei denen ein erneuter Versuch sinnvoll ist:
429 – Too Many Requests (Server lehnt Anfragen wegen Rate Limiting ab)
503 – Service Unavailable (Dienst ist kurzzeitig nicht erreichbar oder überlastet)
Genau für solche Situationen stellt AddStandardResilienceHandler sinnvolle Defaults bereit: Retries mit Backoff, Timeouts pro Versuch, ein globales Request-Timeout sowie ein Circuit Breaker, der bei anhaltenden Fehlern weitere Requests kurzzeitig blockiert.
Das Ergebnis sind robuste HTTP-Calls, die kurzzeitige API-Probleme selbstständig überstehen, externe Abhängigkeiten nicht unnötig weiter belasten und den eigenen Service vor Kettenreaktionen schützen.
Ideal für Web-APIs, Worker Services und alle Anwendungen, die regelmäßig mit externen HTTP-Services sprechen.
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<WeatherClient>(client =>
{
client.BaseAddress = new Uri("https://example.com/");
client.Timeout = Timeout.InfiniteTimeSpan;
})
.AddStandardResilienceHandler(options =>
{
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(20);
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);
options.Retry.MaxRetryAttempts = 3;
options.Retry.Delay = TimeSpan.FromMilliseconds(200);
options.Retry.BackoffType = Microsoft.Extensions.Http.Resilience.HttpRetryBackoffType.Exponential;
options.Retry.UseJitter = true;
options.Retry.ShouldHandle = args =>
args.Outcome.Result is { } r && (
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode == HttpStatusCode.RequestTimeout ||
(int)r.StatusCode >= 500);
options.CircuitBreaker.MinimumThroughput = 20;
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
options.CircuitBreaker.FailureRatio = 0.5;
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(15);
});
var app = builder.Build();
app.MapGet("/weather/{city}", async (string city, WeatherClient client, CancellationToken ct) =>
{
var forecast = await client.GetForecastAsync(city, ct);
return Results.Ok(forecast);
});
app.Run();
public sealed class WeatherClient(HttpClient http)
{
public async Task<string> GetForecastAsync(string city, CancellationToken ct)
{
using var res = await http.GetAsync($"api/forecast?city={Uri.EscapeDataString(city)}", ct);
res.EnsureSuccessStatusCode();
return await res.Content.ReadAsStringAsync(ct);
}
}
1 Kommentare zum Snippet