May 19 2025
Startups canāt afford to lose users over broken features. With TestSpriteās fully autonomous AI testing, you can catch issues early with less effort from your QA teamāand greater accuracy. Ship faster, smarter, and with confidence. Try it now at testsprite.com. Join here
Hey friends! š
In todayās cloud-connected world, weāre constantly making HTTP calls to external APIs, microservices, or third-party integrations. But letās face it: networks can be flaky, services can slow down, and stuff just... breaks.
What if your app could handle those hiccups gracefully without crashing or stressing out your users?
Great news! .NET ships with a first-party library for building resilient apps: Microsoft.Extensions.Resilience.
Think of it like Polly, but built into .NET, fully supported by Microsoft, and super easy to plug into your existing setup.
Letās break it all down in a friendly, real-world way - with full code, strategy-by-strategy explanations, and Minimal API examples.
It's a set of libraries that help you: ⢠Retry failed operations ⢠Set timeouts ⢠Break circuits on repeated failures ⢠Control request rates ⢠Send backup (hedged) requests All of these are called resilience strategies, and you can mix and match them using a composable pipeline. You get full integration with HttpClientFactory, DI, logging, and even OpenTelemetry.
Add required packages:
dotnet add package Microsoft.Extensions.Http.Resiliencedotnet add package Microsoft.Extensions.Resilience
To apply resilience, you need to build a pipeline made up of different resilience strategies. These strategies run in the exact order you define them -so the order really matters.
You begin by creating a ResiliencePipelineBuilder, adding strategies to it, and then calling Build() to get the final pipeline.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions { ShouldHandle = new PredicateBuilder().Handle<ConflictException>(), Delay = TimeSpan.FromSeconds(2), MaxRetryAttempts = 3, BackoffType = DelayBackoffType.Exponential, UseJitter = true }) .AddTimeout(new TimeoutStrategyOptions { Timeout = TimeSpan.FromSeconds(5) }) .Build();Ā await pipeline.ExecuteAsync( async ct => await httpClient.GetAsync("/api/weather", ct), cancellationToken);
Now letās break down each strategy...
A retry strategy lets you retry an operation that failed due to transient issues (like a timeout or HTTP 500).
Why? ⢠Temporary failures are common in distributed systems. ⢠Retry gives the system time to recover. Example:
builder.Services.AddResiliencePipeline("retry-pipeline", builder =>{ builder.AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3, Delay = TimeSpan.FromMilliseconds(300), BackoffType = DelayBackoffType.Exponential, ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>() });});
This retries 3 times, with exponentially increasing delay: 300ms, 600ms, 1200ms...
The timeout strategy sets a max duration for how long an operation can run. If itās too slow - itās out.
Why? ⢠You donāt want threads hanging forever. ⢠Itās better to fail fast and free resources. Example:
builder.Services.AddResiliencePipeline("timeout-pipeline", builder =>{ builder.AddTimeout(TimeSpan.FromSeconds(2));});
If your external service doesnāt respond in 2 seconds, it fails and triggers the next strategy (like retry)...
The circuit breaker temporarily blocks calls to a failing system, preventing overload and giving it time to recover.
Why? ⢠Avoid hammering a broken service. ⢠Let things cool off. Example:
builder.Services.AddResiliencePipeline("cb-pipeline", builder =>{ builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions { FailureRatio = 0.5, MinimumThroughput = 10, SamplingDuration = TimeSpan.FromSeconds(30), BreakDuration = TimeSpan.FromSeconds(15) });});
If 50% of calls fail (with a minimum of 10 calls) within 30 seconds, the breaker "trips" and all further calls fail fast for 15 seconds.
The hedging strategy sends secondary requests after a delay if the primary one is too slow or might fail. Itās like having a plan B running in parallel.
Why? ⢠Reduce latency spikes. ⢠Improve success rate by racing multiple attempts. Example:
builder.Services.AddResiliencePipeline<string, string>("gh-hedging", builder =>{ builder.AddHedging(new HedgingStrategyOptions<string> { MaxHedgedAttempts = 3, DelayGenerator = args => { var delay = args.AttemptNumber switch { 0 or 1 => TimeSpan.Zero, // Parallel mode _ => TimeSpan.FromSeconds(-1) // Fallback mode };Ā return new ValueTask<TimeSpan>(delay); } });});
With this configuration, the hedging strategy:
The fallback strategy provides an alternative response when a primary operation fails, helping your app stay functional even when things go wrong.
Why? ⢠Avoid crashing the user experience. ⢠Stay graceful under pressure. Example:
builder.Services.AddResiliencePipeline<string, string?>("gh-fallback", builder =>{ builder.AddFallback(new FallbackStrategyOptions<string?> { FallbackAction = _ => Outcome.FromResultAsValueTask<string?>(string.Empty) });});
The rate limiter strategy controls how many calls your system can make within a time window - protecting resources and avoiding service throttling.
Why? ⢠Prevent overwhelming dependencies. ⢠Ensure fair usage and system stability. Example:
builder.Services.AddResiliencePipeline("ratelimiter-pipeline", builder =>{ builder.AddRateLimiter(new SlidingWindowRateLimiter( new SlidingWindowRateLimiterOptions { PermitLimit = 100, SegmentsPerWindow = 4, Window = TimeSpan.FromMinutes(1) } ));});
When you're working with resilience in .NET and using Dependency Injection, you donāt need to manually build your pipeline every time.
Instead, you can ask the ResiliencePipelineProvider to fetch a configured pipeline by its key.
Here's how it works:
⢠You define your pipeline (e.g., with a fallback or retry strategy) and register it using a key like "gh-fallback".
⢠Then, inside your route or handler, you request that pipeline using GetPipeline
app.MapGet("/subscribers", async ( HttpClient httpClient, ResiliencePipelineProvider<string> pipelineProvider, CancellationToken cancellationToken) =>{ var pipeline = pipelineProvider.GetPipeline<Subscriber?>("gh-fallback");Ā return await pipeline.ExecuteAsync( async token => await httpClient.GetFromJsonAsync<Subscriber>("api/subscribers", token), cancellationToken);});
Microsoft.Extensions.Resilience is a first-party .NET library for adding resilience to your applications. It provides composable strategies like retry, timeout, circuit breaker, hedging, fallback, and rate limiting. It integrates natively with HttpClientFactory, dependency injection, logging, and OpenTelemetry. It is built on top of Polly v8 and is fully supported by Microsoft.
Polly is the open-source resilience library that Microsoft.Extensions.Resilience builds on. The Microsoft package adds first-class integration with the .NET hosting model: named pipelines via DI, HttpClientFactory support, configuration binding, and telemetry. If you are starting a new project, use Microsoft.Extensions.Http.Resilience for HTTP calls and Microsoft.Extensions.Resilience for non-HTTP operations.
Install the Microsoft.Extensions.Resilience package, then register a named pipeline with AddResiliencePipeline in Program.cs. Add a RetryStrategyOptions with your desired MaxRetryAttempts, Delay, and BackoffType. Inject ResiliencePipelineProvider into your endpoint and call ExecuteAsync to run your operation through the pipeline.
The circuit breaker pattern temporarily stops calls to a failing dependency. When the failure ratio exceeds a threshold within a sampling window, the circuit "opens" and all subsequent calls fail immediately without hitting the downstream service. After a configured break duration, the circuit moves to "half-open" and allows a test call through. If it succeeds, the circuit closes and normal traffic resumes.
Yes. You chain strategies using ResiliencePipelineBuilder. The strategies execute in the order you add them. A common combination is retry wrapping a timeout wrapping a circuit breaker. The outer strategy (added first) wraps the inner ones, so if a timeout fires, the retry strategy can attempt the call again.
Microsoft's Microsoft.Extensions.Resilience stack makes it easier than ever to apply production-grade resilience across your entire .NET application.
You donāt need to reinvent the wheel with Polly yourself - use the official pipeline abstraction, build named strategies, and compose reusable policies that apply to your internal services or third-party APIs.
Use retry + timeout + circuit breaker as your minimum standard for every external call. And if you want even more flexibility, customize the heck out of it!
That's all from me today.
P.S. Follow me on YouTube.
Want to enforce clean code automatically? My Pragmatic .NET Code Rules course shows you how to set up analyzers, CI quality gates, and architecture tests - a production-ready system that keeps your codebase clean without manual reviews. Or grab the free Starter Kit to try it out.
Stop arguing about code style. In this course you get a production-proven setup with analyzers, CI quality gates, and architecture tests ā the exact system I use in real projects. Join here.
Not sure yet? Grab the free Starter Kit ā a drop-in setup with the essentials from Module 01.
Design Patterns that Deliver ā Solve real problems with 5 battle-tested patterns (Builder, Decorator, Strategy, Adapter, Mediator) using practical, real-world examples. Trusted by 650+ developers.
Just getting started? Design Patterns Simplified covers 10 essential patterns in a beginner-friendly, 30-page guide for just $9.95.
Every Monday morning, I share 1 actionable tip on C#, .NET & Architecture that you can use right away. Join here.
Join 20,000+ subscribers who mass-improve their .NET skills with actionable tips on C#, Architecture & Best Practices.
Subscribe to the TheCodeMan.net and be among the 20,000+ subscribers gaining practical tips and resources to enhance your .NET expertise.