Sprache: C#
.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:
[b]429 – Too Many Requests[/b] [u](Server lehnt Anfragen wegen Rate Limiting ab)[/u]
[b]503 – Service Unavailable[/b] [u](Dienst ist kurzzeitig nicht erreichbar oder überlastet)[/u]
Genau für solche Situationen stellt [b]AddStandardResilienceHandler[/b] 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);
}
}
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);
}
}
Alte URL:
/snippet/robuste-http-calls-in-net-8-ohne-eigene-retry-logik/16224
Wenn du [b]nicht .NET 8+[/b] nutzt, kannst du das gleiche Prinzip in .NET 6/7 (oder älter) sehr ähnlich abbilden – typischerweise mit Polly als Policy-Handler am HttpClient. Damit bekommst du wieder Retries (mit Backoff), Timeout pro Versuch und optional Circuit Breaker – nur eben nicht über AddStandardResilienceHandler, sondern über Policies.
[code]using Polly;
using Polly.Extensions.Http;
using System.Net;
builder.Services.AddHttpClient(client =>(TimeSpan.FromSeconds(5)))
{
client.BaseAddress = new Uri(„https://example.com/“);
client.Timeout = Timeout.InfiniteTimeSpan; // Timeout kommt über Policy
})
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(3, retry => TimeSpan.FromMilliseconds(200) * Math.Pow(2, retry)))
.AddPolicyHandler(Policy.TimeoutAsync
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 5, durationOfBreak: TimeSpan.FromSeconds(15)));
[/code]
Damit verhält sich dein Client [b]konzeptionell wie in .NET 8[/b]: kurze API-Aussetzer werden abgefedert, 429/5xx werden sinnvoll behandelt, und bei anhaltenden Fehlern schützt der Circuit Breaker deinen Service vor „Dauerschleifen“.