Modern ASP.NET Core applications rarely operate in isolation. They communicate with databases, payment gateways, cloud storage, authentication providers, messaging systems, and third-party APIs. While these dependencies are essential, they also introduce failures that are outside your application's control.
A temporary network outage, a slow external API, or a throttled cloud service shouldn't immediately cause your application to fail. Instead, applications should recover gracefully, minimize user impact, and protect downstream services from cascading failures.
Polly is the de facto resilience library for .NET, allowing developers to implement retry, circuit breaker, timeout, fallback, and other resilience patterns with minimal code changes. Combined with HttpClientFactory, Polly helps build reliable, production-ready applications.
In this article, you'll learn how to implement resilience policies in ASP.NET Core, understand when to use each strategy, and build a resilient communication pipeline for external services.
Why Resilience Matters
The Reality of Distributed Systems
Consider an order processing application.
Customer
│
▼
ASP.NET Core API
│
┌──┴───────────────┐
▼ ▼
SQL Database Payment Gateway
│
▼
Shipping Provider
If the payment gateway becomes temporarily unavailable:
Orders cannot be completed.
Customer requests fail.
Application reliability decreases.
Support requests increase.
Not every failure requires immediate failure. Many are temporary and recover automatically within seconds.
Understanding Retry
What Is Retry?
Retry automatically repeats an operation after a transient failure.
Typical transient failures include:
Temporary network interruptions
HTTP 503 responses
Connection resets
Cloud service throttling
Instead of failing immediately, the application retries the request after a short delay.
Configuring Retry
Register an HttpClient with a retry policy.
builder.Services
.AddHttpClient<PaymentClient>()
.AddPolicyHandler(
Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(3,
retry =>
TimeSpan.FromSeconds(retry)));
Why Use Retry?
Many failures are temporary.
Retrying a request a small number of times often succeeds without requiring user intervention.
However, retries should remain limited because excessive retries can increase load on already struggling services.
Understanding Circuit Breaker
Retries alone cannot solve every problem.
If a service remains unavailable, repeatedly retrying requests wastes resources.
Circuit Breaker addresses this issue.
builder.Services
.AddHttpClient<InventoryClient>()
.AddPolicyHandler(
Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
5,
TimeSpan.FromSeconds(30)));
Why Use a Circuit Breaker?
After several consecutive failures:
Requests stop reaching the failing service.
Resources are preserved.
Recovery time improves.
Cascading failures are reduced.
After the configured break period expires, the circuit allows limited traffic to determine whether the service has recovered.
Configuring Timeouts
Waiting indefinitely for an external service reduces application throughput.
builder.Services
.AddHttpClient<ShippingClient>()
.AddPolicyHandler(
Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10)));
Why Use Timeouts?
Timeouts prevent slow external services from consuming request threads indefinitely.
Combined with retries and circuit breakers, they help maintain predictable response times during dependency failures.
Implementing Fallback
Sometimes an alternative response is preferable to a failure.
Policy<string>
.Handle<Exception>()
.FallbackAsync("Service temporarily unavailable.");
Why Use Fallback?
Fallback provides a controlled response when every other resilience strategy fails.
Examples include:
Returning cached data
Displaying maintenance information
Serving default configuration
Returning partial results
Fallback should provide useful behavior rather than simply masking failures.
Combining Policies
Production applications rarely use a single resilience strategy.
Typical execution order:
Request
│
▼
Timeout
│
Retry
│
Circuit Breaker
│
Fallback
│
External Service
Each policy addresses a different failure scenario.
Together they create a more reliable communication pipeline.
End-to-End Implementation
Consider an online retail platform.
Architecture:
Customer
│
▼
Order API
│
HttpClientFactory
│
Polly Policies
│
┌───┴─────────────────────┐
▼ ▼
Payment API Shipping API
Join the conversation! Your thoughts help the community grow.