ASP.NET Core  

Designing Resilient ASP.NET Core APIs Using .NET 8 Resilience Pipelines

Introduction

For years, Polly was the go-to library for building resilient .NET applications. Retry policies, circuit breakers, timeouts—everything lived in Polly.

Starting with .NET 8, Microsoft introduced built-in Resilience Pipelines, marking a major shift in how fault tolerance is implemented in ASP.NET Core.

This article explores:

  • What resilience pipelines are

  • How they differ from Polly

  • How to build retry, timeout, and circuit breaker pipelines

  • Real-world API examples

  • Best practices for production systems

Why Resilience Matters in Distributed Systems

In modern systems:

  • APIs depend on external services

  • Networks fail

  • Latency spikes

  • Downstream services become unstable

Without resilience:

  • One failure cascades

  • Threads block

  • APIs crash under load

Resilience pipelines solve this inside the framework, not via third-party middleware.

What Are Resilience Pipelines?

A Resilience Pipeline is a composable execution flow that applies fault-handling strategies such as:

  • Retry

  • Timeout

  • Circuit Breaker

  • Hedging

  • Rate Limiting

They are:

  • Built on System.Threading.RateLimiting

  • Fully async

  • Dependency-injection friendly

  • Designed for cloud-native workloads

Adding Resilience Support

Required Package

dotnet add package Microsoft.Extensions.Resilience

Configuring a Resilience Pipeline

Step 1: Register Pipelines in Program.cs

using Microsoft.Extensions.Resilience;
using Polly.Timeout;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResiliencePipeline("external-api", pipeline =>
{
    pipeline
        .AddTimeout(TimeSpan.FromSeconds(3))
        .AddRetry(new RetryStrategyOptions
        {
            MaxRetryAttempts = 3,
            Delay = TimeSpan.FromMilliseconds(500),
            BackoffType = DelayBackoffType.Exponential
        })
        .AddCircuitBreaker(new CircuitBreakerStrategyOptions
        {
            FailureRatio = 0.5,
            SamplingDuration = TimeSpan.FromSeconds(30),
            MinimumThroughput = 10,
            BreakDuration = TimeSpan.FromSeconds(15)
        });
});

builder.Services.AddHttpClient<WeatherClient>()
    .AddResilienceHandler("external-api");

var app = builder.Build();
app.MapControllers();
app.Run();

What’s Happening Here?

StrategyPurpose
TimeoutPrevents thread starvation
RetryHandles transient failures
Circuit BreakerStops cascading failures

Unlike Polly, this pipeline is

  • Centralized

  • Declarative

  • Observable

Using Resilience in a Typed HTTP Client

Step 2: Create a Typed Client

public class WeatherClient
{
    private readonly HttpClient _httpClient;

    public WeatherClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetWeatherAsync()
    {
        var response = await _httpClient.GetAsync("https://external-api/weather");
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

Failures here will automatically:

  • Retry

  • Timeout

  • Open circuit when unhealthy

Exposing the API Endpoint

[ApiController]
[Route("api/weather")]
public class WeatherController : ControllerBase
{
    private readonly WeatherClient _client;

    public WeatherController(WeatherClient client)
    {
        _client = client;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var result = await _client.GetWeatherAsync();
        return Ok(result);
    }
}

Advanced: Using Hedging for Faster Responses

Hedging sends multiple parallel requests and uses the fastest response.

pipeline.AddHedging(new HedgingStrategyOptions
{
    MaxHedgedAttempts = 2,
    Delay = TimeSpan.FromMilliseconds(200)
});

This is ideal for:

  • High-latency APIs

  • Read-heavy services

  • Geo-distributed systems

Observability and Metrics

Resilience pipelines integrate with:

  • OpenTelemetry

  • ILogger

  • Metrics providers

Example:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation();
        metrics.AddHttpClientInstrumentation();
    });

You get visibility into:

  • Retry counts

  • Circuit breaker states

  • Timeout failures

When to Use Resilience Pipelines vs Polly?

ScenarioRecommendation
New .NET 8+ appsResilience Pipelines
Legacy systemsPolly
Deep customizationPolly
Cloud-native APIsResilience Pipelines

Production Best Practices

✔ Avoid retries on non-idempotent requests
✔ Keep timeouts short
✔ Monitor circuit breaker states
✔ Combine with rate limiting
✔ Test failure scenarios locally

Why This Matters for .NET Developers

Resilience Pipelines represent:

  • Microsoft’s future direction

  • A move away from external dependencies

  • A standardized approach to fault tolerance

Ignoring this feature means missing one of the most important architectural improvements in modern .NET.

Conclusion

With .NET 8, resilience is no longer an afterthought or a third-party concern. Resilience pipelines offer a powerful, built-in, and production-ready way to build fault-tolerant ASP.NET Core applications.

If you’re building APIs for real-world traffic, this feature is no longer optional—it’s essential.