In my original Polly article, I introduced Polly as a robust resilience framework for handling transient faults and service failures. At the time, Polly was entirely community-driven and required manual configuration within each application. Fast forward five years, and Microsoft has officially embraced the concepts introduced by Polly, incorporating them directly into the .NET framework with a suite of first-party resilience libraries.
This article explores how fault tolerance has evolved in .NET, highlighting the new Microsoft.Extensions.Resilience and Microsoft.Extensions.Http.Resilience libraries. These packages allow developers to configure and apply resilience strategies like retry policies, circuit breakers, and timeouts using cleaner, more integrated code.
What Has Changed?
Historically, .NET developers had to install Polly via NuGet, manually define resilience strategies, and wire them into their HTTP clients and service calls. This approach, while powerful, required a deeper understanding of Polly’s API and how to properly manage dependencies and middleware in an application.
With .NET 8, Microsoft introduced Microsoft.Extensions.Resilience and related packages. These libraries:
- Integrate directly with the .NET dependency injection system.
- Offer predefined, composable strategies out-of-the-box.
- Reduce configuration overhead.
- Enhance observability by plugging into diagnostics and OpenTelemetry.
- Simplify testing and monitoring with a consistent programming model.
Microsoft has essentially brought resilience into the core of .NET, giving developers native tooling to build robust, production-grade systems.
Setting Up the Resilience Framework
To get started with the new resilience capabilities in .NET 8, you’ll need to install the following packages:
dotnet add package Microsoft.Extensions.Resilience
dotnet add package Microsoft.Extensions.Http.Resilience
These packages allow you to add prebuilt resilience handlers to your HTTP clients and define custom policies for use elsewhere in your application. The integration with IHttpClientFactory means you can set up these handlers declaratively in your Startup.cs or program configuration.
A Simple Example with HttpClient
Let’s look at how easy it is to configure a resilient HTTP client. Below is an example that creates a named client with a standard resilience pipeline:
builder.Services
.AddHttpClient("resilient-client")
.AddStandardResilienceHandler(options =>
{
options.TotalRequestTimeout = TimeSpan.FromSeconds(10);
options.Retry.MaxRetryAttempts = 3;
options.CircuitBreaker.FailureThreshold = 0.5;
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
});
This setup gives you retry, timeout, and circuit breaker policies configured with sensible defaults. You can then inject this client into your services:
public class MyService
{
private readonly HttpClient _httpClient;
public MyService(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("resilient-client");
}
public async Task<string> GetDataAsync()
{
var response = await _httpClient.GetAsync("https://example.com/api/data");
return await response.Content.ReadAsStringAsync();
}
}
This level of simplicity allows developers to quickly adopt best practices without needing to dive deeply into Polly internals.
Customising the Pipeline
While the standard handler is excellent for most scenarios, Microsoft also allows complete control by defining a custom pipeline. This is useful when you need to adjust parameters for retry timing, circuit breaker thresholds, or other strategies.
builder.Services
.AddResiliencePipelineBuilder("custom-pipeline")
.AddTimeout(TimeSpan.FromSeconds(5))
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureThreshold = 0.25,
SamplingDuration = TimeSpan.FromSeconds(20),
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30)
});
You can then bind this custom pipeline to an HTTP client:
builder.Services
.AddHttpClient("custom-client")
.AddResilienceHandler("custom-pipeline");
The flexibility here means you can tailor fault tolerance to the unique behaviours and requirements of each endpoint.
Adding Fallback Logic
Fallback logic is used when a request fails and you want to return a default value or alternate result. This is particularly useful in microservice or distributed systems where availability is more critical than accuracy in some cases.
.AddFallback(new FallbackStrategyOptions<HttpResponseMessage>
{
ShouldHandle = args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode == HttpStatusCode.InternalServerError),
FallbackAction = args => ValueTask.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{ \"message\": \"Default response\" }")
})
});
In this example, if the external API returns a 500 error, the fallback will trigger and return a default JSON payload.
Bulkhead Isolation (Back Again, But Better)
Bulkhead isolation prevents one failing component from exhausting system resources and affecting others. It limits the number of concurrent actions and optionally queues overflow requests.
.AddConcurrencyLimiter(new ConcurrencyLimiterStrategyOptions
{
PermitLimit = 2, // Max concurrent executions
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 4
});
This is especially important in high-traffic applications or when calling slow or unreliable external services.
When all slots and queues are full, the framework throws a BulkheadRejectedException, allowing you to log or trigger alerts appropriately.
Monitoring and Observability
A major advantage of Microsoft’s approach is deep integration with observability tools. You can use diagnostic listeners or OpenTelemetry to track resilience behaviour.
builder.Services.AddOpenTelemetry()
.WithTracing(tracerBuilder =>
tracerBuilder
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
);
This enables tracing of retries, timeouts, and circuit breaker activity in tools like Jaeger, Zipkin, or Azure Monitor.
Summary
Microsoft’s new resilience framework in .NET 8 significantly improves how we manage fault tolerance. By building upon Polly and deeply integrating it into the .NET ecosystem, Microsoft makes resilience strategies easier to configure, maintain, and observe.
If you previously implemented Polly manually, the transition to Microsoft’s official packages can reduce complexity and align your application with future .NET best practices.