# Microsoft Resiliency Extensions and Polly Part 1 - Building Your First Resilience Pipeline

> Handle transient failures in .NET with Microsoft.Extensions.Http.Resilience and Polly v8: build a resilience pipeline with DI and see why strategy order matters.

- URL: https://blog.nimblepros.com/blogs/building-your-first-resilience-pipeline/
- Author: [Jeff Zuerlein](https://blog.nimblepros.com/authors/jeffz/)
- Published: 2025-07-10
- Updated: 2026-11-06
- Categories: [Software Development](https://blog.nimblepros.com/categories/software-development/)
- Publisher: [NimblePros](https://blog.nimblepros.com/)

---

Let’s suppose you’re working on an application that experiences transient faults—for example, a REST service you depend on occasionally fails to respond. The root cause might be infrastructure instability, network flakiness, or a downstream service that's simply outside your control. You can’t fix the dependency, but you can change how your application responds to these failures. That’s where Microsoft’s resiliency extensions—and Polly—come to the rescue.

Polly is a popular open-source library that’s helped .NET developers implement robust fault-handling strategies like retries, timeouts, and circuit breakers for years. With the release of version 8, Polly was significantly redesigned for performance and composability, and now forms the foundation of the Microsoft.Extensions.Resilience and Microsoft.Extensions.Http.Resilience packages. In fact, if you dig into the dependencies of these libraries, you’ll find Polly right there powering the entire resilience pipeline.

## Microsoft.Extensions.Http.Resilience ##

Microsoft has introduced extension methods that make it easy to apply standardized resilience strategies to HttpClient instances. For many scenarios, this can be a great shortcut—a simple, one-size-fits-most approach to building resilience into your HTTP calls.

```csharp
services
    .AddHttpClient("my-client")
    .AddStandardResilienceHandler(options =>
    {
        // Customize retry logic
        options.Retry.ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .Handle<TimeoutRejectedException>()
            .Handle<HttpRequestException>()
            .HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError);
        
        options.Retry.MaxRetryAttempts = 5;

        // Customize attempt timeout
        options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(2);
    });
```

The `AddStandardResilienceHandler` method wires up a comprehensive resilience pipeline with built-in strategies for:
- Rate limiting
- Total request timeout
- Retry logic
- Circuit breaking
- Per-attempt timeouts

That’s a lot of functionality implemented with just a few lines of code—and the options object gives you the flexibility to fine-tune each strategy as needed.  Microsoft also provides several other standardized handler sets, each designed for common usage patterns. These can save time and ensure consistent resilience defaults for teams that want consistancy.  But if your scenario calls for more control, or you want to implement resilience for non-HTTP code, you’re not limited to the built-in handlers. You can build custom pipelines using the same powerful engine under the hood: Polly’s `ResiliencePipeline`.

## What Are Resilience Pipelines?

Implementing a resiliency strategy in .NET is accomplished using a `ResiliencePipeline`. This pipeline acts as a container for one or more resilience strategies—like retries, timeouts, rate limiting, or circuit breakers—and executes your code (such as HTTP requests or database queries) through them.

Let’s walk through an example that shows how to define a pipeline to make an HTTP request with a timeout and a retry policy in case the first attempt fails.

## Defining a Custom Resilience Pipeline with DI

In this example, we’ll define a pipeline using an extension method on `IServiceCollection`. This is a convenient way to register a reusable pipeline in your application's dependency injection container.

```csharp
public static class ResiliencePipelineExtensions
{
    public static IServiceCollection AddCustomResiliencePipeline(this IServiceCollection services)
    {
        services.AddResiliencePipeline("http-pipeline", builder =>
        {
            // Retry strategy
            builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
            {
                MaxRetryAttempts = 3,
                ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                    .Handle<TimeoutRejectedException>()
                    .HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError)
            });

            // Timeout per attempt
            builder.AddTimeout(TimeSpan.FromSeconds(2));
        });

        return services;
    }
}
```

In this code:
- `ResiliencePipelineBuilder<T>` is used to compose strategies like `.AddRetry()` and `.AddTimeout()`.
- `RetryStrategyOptions<T>` lets you configure how and when retries should happen:
- `MaxRetryAttempts` controls how many times a failed attempt will be retried.
- `ShouldHandle` defines the conditions that should trigger a retry.
- `AddTimeout` accepts a TimeSpan that specifies how long the pipeline should allow the executing code to run before throwing a timeout exception.

`PredicateBuilder<T>` helps you compose readable conditions, such as retrying on a `TimeoutRejectedException` or a specific HttpStatusCode.  This makes the retry logic expressive and easy to extend. You could just as easily handle other exceptions or HTTP response codes by chaining more conditions.

## Understanding Strategy Order in a Resilience Pipeline

The order in which you add strategies to the pipeline matters. The first strategy added to the pipeline wraps the next one that is added.  That means that the behaviour of the pipeline depends on the order. Looking at an example is the best way to understand why.

### Original Strategy Order: Retry Outside, Timeout Inside ###

```csharp
builder
    .AddRetry(...)        // outer
    .AddTimeout(...)      // inner
```

In this setup, each attempt is wrapped in a timeout.  If the code takes longer than the timeout (e.g., 2 seconds), it throws a `TimeoutRejectedException`.  The retry strategy catches that timeout exception and triggers another attempt.

Key behavior : Every retry gets its own full 2-second timeout. So if `MaxRetryAttempts` is 3, the operation could take up to 6 seconds total (3 × 2 seconds).

### Reversed Order: Timeout Outside, Retry Inside ###

```csharp
builder
    .AddTimeout(...)      // outer
    .AddRetry(...)        // inner
```

This changes everything.  Now, the entire retry sequence must complete within the 2-second timeout.  If the first attempt takes too long (say, 1.5 seconds), the retry may or may not happen before the 2-second timeout is hit.  If a retry is attempted, it has to finish quickly, or the entire pipeline will timeout.
 
Key behavior : The timeout limits the total duration of all retries combined.

### Choosing the Right Order ###
- Retry outside, timeout inside (the default in most docs): Best for retrying individual transient failures that should be bounded in time.
- Timeout outside, retry inside: Useful when you need to cap the total execution time strictly (e.g., SLA-driven systems or APIs with aggressive response-time budgets).

This subtle ordering can make a big difference in how your resilience strategy behaves. Fortunately, Polly v8’s `ResiliencePipelineBuilder` gives you full control.
 
## Using a Resilience Pipeline with an HTTP Client

Now that we’ve set up our `ResiliencePipeline`, it’s time to use it in actual application code.
In this example, I’m calling the National Weather Service’s REST API using a custom client class that leverages the Polly pipeline to handle transient failures cleanly.  Since we registered the `ResiliencePipeline` in dependency injection, we can inject the `ResiliencePipelineProvider` into our class constructor. This provider gives us access to the named pipeline we registered earlier:

```csharp
public class WeatherApiClient
{
    private readonly ResiliencePipeline _pipeline;

    public WeatherApiClient(ResiliencePipelineProvider<string> pipelineProvider)
    {
        _pipeline = pipelineProvider.GetPipeline("name-of-pipeline");
    }
}
```

Once you have an instance of the pipeline, you can use its `ExecuteAsync` method to wrap any operation you want to protect with your defined strategies.
Here's how that might look:

```csharp
public async Task<HttpResponseMessage> GetAsync(string url, CancellationToken cancellationToken)
{
    using var httpClient = new HttpClient();

    return await _pipeline.ExecuteAsync<HttpResponseMessage>(
        async token => await httpClient.GetAsync(url, token));
}
```

Let’s break it down.  We’re passing a callback function to `ExecuteAsync` that performs the actual HTTP GET request.  The `ResiliencePipeline` will:
- Timeout the request if it takes longer than 2 seconds (as configured earlier).
- Retry the request (up to 2 times) if it fails with a timeout or a qualifying error like an HTTP 500.

All of that retry and timeout logic is defined once, when the pipeline is registered. That means your business logic stays clean, and resilience becomes a cross-cutting concern handled by configuration—not boilerplate code.

### Why This Matters

By using `ResiliencePipeline`:
- You gain consistency across all service calls.
- Your retry logic is centralized and easy to test or modify.
- You reduce the risk of copy-paste bugs from rolling your own retry/timeout logic repeatedly.

## Conclusion ##

Building applications that can withstand transient failures—whether due to infrastructure hiccups or unreliable external services—is far easier when you leverage Microsoft’s Resilience APIs and Polly v8. These tools offer everything from ready-to-use, standardized HTTP strategies to fully customizable resilience pipelines.

By understanding how these pipelines work and how to configure them, you can design resilience strategies that are tailored to your application's specific needs, rather than relying on hardcoded retry loops or ad-hoc error handling.
In the next article, we’ll dive into telemetry and observability—exploring how you can monitor what’s happening inside your resilience pipelines, measure failures, and gain deeper insight into how your application behaves under stress.

## Resources ##

- [Microsoft.Extensions.Http.Resilience](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.http.resilience)
- [Polly](https://www.pollydocs.org/)
