šŸ”„ Pragmatic .NET Code Rules Course is on Presale - 40% off!BUY NOW

Building Resilient APIs in ASP.NET Core

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

Background

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.

What is Microsoft.Extensions.Resilience?

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.

Project Setup

Add required packages:

C#
dotnet add package Microsoft.Extensions.Http.Resiliencedotnet add package Microsoft.Extensions.Resilience

Resilience Pipelines

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.

C#
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...

Retry Strategy

"Try again if it fails... but not forever."

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:

C#
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...

Timeout Strategy

"Don’t hang forever. Bail out if it’s too slow."

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:

C#
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)...

Circuit Breaker Strategy

"If something keeps failing, stop trying for a while."

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:

C#
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.

Hedging Strategy

"If you're not getting a reply, ask someone else too - just in case."

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:

C#
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:

  • Initiates a maximum of 4 executions. This includes initial action and an additional 3 attempts.
  • Allows the first two executions to proceed in parallel, while the third and fourth executions follow the fallback mode.

Fallback Strategy

"If something keeps failing, have a backup plan."

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:

C#
builder.Services.AddResiliencePipeline<string, string?>("gh-fallback", builder =>{ builder.AddFallback(new FallbackStrategyOptions<string?> { FallbackAction = _ => Outcome.FromResultAsValueTask<string?>(string.Empty) });});

Rate Limiting Strategy

"Too many requests? Slow down before you burn out."

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:

C#
builder.Services.AddResiliencePipeline("ratelimiter-pipeline", builder =>{ builder.AddRateLimiter(new SlidingWindowRateLimiter( new SlidingWindowRateLimiterOptions { PermitLimit = 100, SegmentsPerWindow = 4, Window = TimeSpan.FromMinutes(1) } ));});

Using ResiliencePipelineProvider to Apply Resilience Automatically

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(), where T is the expected result type.

C#
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);});

Frequently Asked Questions

What is Microsoft.Extensions.Resilience?

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.

What is the difference between Polly and Microsoft.Extensions.Resilience?

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.

How do I add retry logic to an ASP.NET Core API?

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.

What is the circuit breaker pattern in .NET?

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.

Can I combine multiple resilience strategies in one pipeline?

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.

Wrapping Up

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.

About the Author

Stefan Djokic is a Microsoft MVP and senior .NET engineer with extensive experience designing enterprise-grade systems and teaching architectural best practices.

There are 3 ways I can help you:

1. Pragmatic .NET Code Rules Course

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.

2. Design Patterns Ebooks

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.

3. Join 20,000+ subscribers

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
TheCodeMan.net

Subscribe to the TheCodeMan.net and be among the 20,000+ subscribers gaining practical tips and resources to enhance your .NET expertise.