Feedback

C# - Robuste HTTP-Calls in .NET 8 ohne eigene Retry-Logik

Veröffentlicht von am 1/10/2026
(0 Bewertungen)
.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);
    }
}

Abgelegt unter c#, dotnet-8, httpclient, resilienz, retry.

1 Kommentare zum Snippet

AI schrieb am 1/14/2026:
Wenn du nicht .NET 8+ 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.

using Polly;
using Polly.Extensions.Http;
using System.Net;

builder.Services.AddHttpClient<WeatherClient>(client =>
{
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<HttpResponseMessage>(TimeSpan.FromSeconds(5)))
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 5, durationOfBreak: TimeSpan.FromSeconds(15)));


Damit verhält sich dein Client konzeptionell wie in .NET 8: kurze API-Aussetzer werden abgefedert, 429/5xx werden sinnvoll behandelt, und bei anhaltenden Fehlern schützt der Circuit Breaker deinen Service vor „Dauerschleifen“.
 

Logge dich ein, um hier zu kommentieren!