In today’s fast-paced world of distributed systems and cloud-based applications, resilience is more than just a buzzword—it’s a necessity. Resilience in .NET applications refers to the ability of software to handle failures gracefully, recover from disruptions, and continue providing service without significant downtime.
This article dives deep into what resilience means in .NET, practical strategies to implement it, and real-world examples that demonstrate its value.
What is Resilience in Software?
Resilience is the capacity of a system to recover from faults and adapt to changing conditions while maintaining acceptable service levels. In a world where services communicate over networks, rely on third-party APIs, and run in unpredictable environments, things will go wrong. Resilient applications anticipate these failures and are designed to manage them effectively.
Why is Resilience Important in .NET?
Modern .NET applications often rely on:
Third-party APIs (e.g., payment gateways, weather services).
Cloud Services like Azure or AWS.
Databases and Queues running on remote servers.
What happens if:
A network glitch causes a request to fail?
A third-party API exceeds its rate limit?
A database query takes longer than expected?
Without resilience, these failures could lead to application crashes, poor user experiences, or even financial loss.
Strategies for Building Resilience in .NET
Resilience is about preparing for the worst. Let’s explore how you can implement resilience using Polly, a popular .NET library for handling transient faults.
1. Retry Logic
Retries are the simplest way to handle transient faults like network interruptions.
Example: Retry with Exponential Backoff
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 2, 4, 8 seconds
(exception, timeSpan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timeSpan}: {exception.Message}");
});
var response = await retryPolicy.ExecuteAsync(async () =>
{
Console.WriteLine("Trying to make an HTTP request...");
return await httpClient.GetAsync("https://example.com");
}); How it works: If the first attempt fails, it retries after 2 seconds, then 4 seconds, then 8 seconds.
Why it’s useful: Handles temporary issues like server unavailability.
2. Circuit Breaker
The circuit breaker pattern prevents overwhelming a failing service by stopping retries after repeated failures.
Example: Circuit Breaker
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(30),
onBreak: (exception, duration) =>
{
Console.WriteLine($"Circuit opened for {duration.TotalSeconds} seconds due to: {exception.Message}");
},
onReset: () => Console.WriteLine("Circuit reset."),
onHalfOpen: () => Console.WriteLine("Circuit half-open."));
try
{
var response = await circuitBreakerPolicy.ExecuteAsync(async () =>
{
Console.WriteLine("Making an HTTP request...");
return await httpClient.GetAsync("https://example.com");
});
}
catch (Exception ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}How it works: After 2 failures, the circuit opens, stopping further calls for 30 seconds.
Why it’s useful: Prevents cascading failures and reduces system strain.
3. Timeouts
Define a time limit for operations to avoid indefinite waits.
Example: Timeout Policy

Join the conversation! Your thoughts help the community grow.