Resilience Architecture in .Net Core is generally implementing a failure handling strategy. When number of calls to any remote source like API, Microservice or database is being made by API, then it is quite possible that some failure can occur, then there should be a way to handle that failure, retry attempts, limits of retry, limiting API to make further calls until the fault is resolved. The resilience architecture is for this purpose.
In today’s distributed and cloud-driven software landscape, failures are inevitable. Networks break, APIs time out, and databases become temporarily unavailable. Modern systems must not only perform well under normal conditions but also gracefully handle unexpected disruptions.
This is where Resilience Architecture in .NET Core comes into play.
Resilience architecture focuses on designing systems that can recover from transient faults, degraded dependencies, and unexpected events without compromising the overall system stability. In the .NET ecosystem, this often involves patterns and tools that ensure your applications can retry, fallback, and self-heal — maintaining reliability even in the face of failures.
Why Resilience Architecture Matters ?
When building microservices or cloud-native applications, components often depend on:
- External APIs
- Databases
- Message brokers
- Cloud services
These dependencies can fail intermittently due to network issues, rate limiting, or service overloads. Without resilience strategies, your system might:
- Crash or hang on transient errors.
- Overwhelm downstream services.
- Fail to recover automatically.
Resilience ensures:
- Stability: Prevents cascading failures.
- Continuity: Keeps critical features running during partial outages.
- User Experience: Minimizes downtime and service interruptions.
How It Works?
- Retry Logic
The system automatically retries failed calls a few times (with delay) — useful for temporary issues. - Circuit Breaker
If failures continue, the circuit “opens” — stopping further calls for a while to avoid overload and allow recovery. - Timeouts
Each call has a max wait time. If it takes too long, it fails fast instead of hanging. - Fallbacks
If all else fails, the system can return a default response or cached data to keep the app responsive. - Tools Used
In .NET Core, this is often implemented using Polly with HttpClientFactory.
Common Failure Handling Strategies
| Strategy | Purpose |
| Retry | Automatically retry failed calls (e.g., due to transient network issues) |
| Retry Limits | Prevent infinite loops or overloading the remote service |
| Circuit Breaker | Stop calling a failing service temporarily to allow recovery |
| Timeouts | Avoid hanging requests by setting maximum wait times |
| Bulkhead Isolation | Isolate failures to prevent them from affecting the whole system |
| Fallbacks | Provide default behavior or cached data when a service is unavailable |
How to implement Resilience Architecture in .Net Core?
We will implement this in a demo application in which we will simulate attempt to access a remote API, the call will fail, and we will handle that failure in resilience pattern design. We will also make sure the calls do not fail indefinitely so we will implement a circuit breaker strategy. We will then use a fallback method to handle what if all calls fail or finish.
Our application will use Retry, Circuit Breaker.
Let’s create a C# console application. Create a folder resilience-architecture-dot-net-core-demo
dotnet new console
Install 2 packages
Microsoft.Extensions.Hosting
Microsoft.Extensions.Resilience
First, create a class TestAPIService.CS and put this code
using System.Net;
/* A simulated API service that fails 3 times before succeeding */
namespace resilience_architecture_dot_net_core_demo
{
public class TestAPIService
{
private int _attemptCount = 0;
private const int FailUntilAttempt = 3;
public async Task<string> GetDataAsync()
{
_attemptCount++;
Console.WriteLine($"\n--- Service Call Attempt {_attemptCount} ---");
if (_attemptCount <= FailUntilAttempt)
{
Console.WriteLine($"[Service] Fails with HTTP 500.");
throw new HttpRequestException("Simulated 500 Internal Server Error", null, HttpStatusCode.InternalServerError);
}
else
{
Console.WriteLine($"[Service] Succeeds!");
return await Task.FromResult("Success: Data Retrieved on attempt " + _attemptCount);
}
}
}
}
Explanation of above code:
This class — TestAPIService — is a simulated API service.
Its purpose is to mimic a real-world unreliable service that fails several times before eventually succeeding.
It’s commonly used in resilience testing — for example, to test retry, circuit breaker, or fallback policies using libraries like Polly.
The purpose of the TestAPIService class is to simulate an unreliable external API that intentionally fails a few times before succeeding, allowing developers to test and demonstrate resilience mechanisms like retries, circuit breakers, and fallbacks in .NET Core.
By throwing an HttpRequestException for the first few calls and returning a success response afterward, it mimics real-world transient failures such as temporary network issues or server overloads. This helps developers validate that their resilience strategies—implemented using tools like Polly—can detect, handle, and recover from intermittent errors without crashing the application.
Now, create a class and name it ResilienceServiceCollectionExtension.Cs and put this code
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.CircuitBreaker;
using Polly.Retry;
namespace resilience_architecture_dot_net_core_demo
{
public static class ResilienceServiceCollectionExtension
{
public static IServiceCollection AddResilienceLayer(this IServiceCollection services)
{
services.AddResiliencePipeline<string>("CustomPipeline", pipelineBuilder =>
{
Console.WriteLine("--- Configuring Resilience Pipeline ---");
// Add Retry Strategy
pipelineBuilder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = args => new ValueTask<bool>(args.Outcome.Exception is HttpRequestException),
OnRetry = args =>
{
Console.WriteLine($"[Resilience] Retrying after failure. Attempt: {args.AttemptNumber}...");
return default;
}
});
// Add Circuit Breaker Strategy
pipelineBuilder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 3,
SamplingDuration = TimeSpan.FromSeconds(10),
BreakDuration = TimeSpan.FromSeconds(5),
ShouldHandle = args => new ValueTask<bool>(args.Outcome.Exception is HttpRequestException),
OnHalfOpened = args => { Console.WriteLine($"[Circuit] State: Half-Open (Trial call will be allowed)."); return default; },
OnOpened = args => { Console.WriteLine($"[Circuit] State: OPEN (Blocking requests for 5s!)."); return default; },
OnClosed = args => { Console.WriteLine($"[Circuit] State: Closed (Service is healthy)."); return default; }
});
});
return services;
}
}
}
Explanation of above code:
The ResilienceServiceCollectionExtension class defines an extension method for the .NET Core Dependency Injection (DI) system.
Its purpose is to configure a resilience layer — specifically a custom resilience pipeline — that automatically applies retry and circuit breaker strategies to protect your application from transient or recurring failures (like network timeouts or HTTP 500 errors).
This uses the new Polly v8 Resilience Pipeline API integrated with Microsoft.Extensions.DependencyInjection, which is a modern and cleaner approach than manually wrapping code with individual policies.
This class configures a reusable, fault-tolerant resilience pipeline in .NET Core that automatically retries transient failures and blocks unstable services using Polly’s retry and circuit breaker strategies, ensuring your application remains stable under unreliable conditions.
Final step is to use all this in Program.CS
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly.Registry;
using resilience_architecture_dot_net_core_demo;
internal class Program
{
private static async Task Main(string[] args)
{
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<TestAPIService>();
builder.Services.AddResilienceLayer();
using IHost host = builder.Build();
var serviceProvider = host.Services;
var apiService = serviceProvider.GetRequiredService<TestAPIService>();
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>();
var pipeline = pipelineProvider.GetPipeline("CustomPipeline");
Console.WriteLine("--- Starting Resilient Call Block ---");
try
{
Console.Clear();
string result = await pipeline.ExecuteAsync(async token =>
{
return await apiService.GetDataAsync();
});
Console.WriteLine($"\n[Application Result] {result}");
}
catch (Exception ex)
{
Console.WriteLine($"\n[Application Error] Operation failed after all retries and circuit breaker logic: {ex.Message}");
}
await host.RunAsync();
}
}
Explanation of above code:
This program demonstrates resilient service communication by using Polly’s resilience pipeline to wrap calls to an unreliable API (TestAPIService), automatically handling retries, circuit breaking, and error recovery — ensuring the application stays stable even when transient failures occur.
Understand all the code
The following three files — TestAPIService.cs, ResilienceServiceCollectionExtension.cs, and Program.cs — work together to form a complete resilience architecture in .NET Core using Polly.
TestAPIService.cs — Simulating a Flaky External Service
This class represents a mock or simulated API that purposely fails several times before succeeding.
It’s designed to mimic real-world transient faults — such as intermittent network errors or temporary service unavailability.
What It Does
- It keeps an internal counter of how many times the service has been called.
- The first three calls throw a simulated
HttpRequestException (500)to imitate a server error. - On the fourth call, the service recovers and successfully returns data.
This mock service creates a controlled failure scenario — allowing developers to test whether their resilience mechanisms (like retries and circuit breakers) behave correctly without depending on actual failing systems.
So by default, the service fails three times and succeeds on the fourth call — the perfect candidate to test resilience logic.
ResilienceServiceCollectionExtension.cs — Defining the Resilience Layer
This file defines an extension method (AddResilienceLayer) that adds a custom Polly-based resilience pipeline to the .NET Core dependency injection container.
What It Does
- It registers a named resilience pipeline (here called
"CustomPipeline") that applies two critical resilience strategies:- Retry Strategy
- Circuit Breaker Strategy
These are chained together to form a layered protection system around your API call.
Retry Strategy
- Automatically retries failed requests up to three times.
- Uses exponential backoff — waiting 1s, then 2s, then 4s between attempts.
- Handles only specific exceptions (
HttpRequestException). - Logs a message every time a retry occurs.
Purpose: Smooths over temporary issues — e.g., a short network blip or a service restart — without impacting the end user.
Circuit Breaker Strategy
- Monitors call outcomes over a time window.
- If more than 50% of requests fail within 10 seconds, and at least 3 requests have been made:
- It opens the circuit for 5 seconds — blocking further requests to the failing service.
- After the break period, the circuit moves to half-open (allowing a test call).
- If the test succeeds, it closes again (normal operation resumes).
Purpose: Protects your application from hammering a failing service repeatedly, giving it time to recover.
Combined Behavior
Together, these two strategies create a resilience shield:
- Retry handles short-term transient failures.
- Circuit breaker prevents continuous long-term stress on a failing dependency.
This entire configuration is reusable and can be easily injected anywhere in your application.
Program.cs — Executing the Resilient Call
This file brings everything together.
It creates a host environment, registers dependencies, and executes an API call through the configured resilience pipeline.
The combined design of these three files demonstrates how to implement a resilient architecture in .NET Core using Polly’s modern resilience pipeline.
- The
TestAPIServiceprovides a realistic failure scenario. - The
ResilienceServiceCollectionExtensionbuilds a configurable and reusable resilience layer using Retry and Circuit Breaker patterns. - The
Program.csorchestrates everything — executing resilient service calls safely.
Together, they illustrate how resilience patterns allow your applications to self-heal, maintain stability, and gracefully recover from transient or repeated service failures — a fundamental principle of modern cloud-native and microservice architectures.
Final Output
--- Service Call Attempt 1 ---
[Service] Fails with HTTP 500.
warn: Polly[3]
Execution attempt. Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error', Handled: 'True', Attempt: '0', Execution Time: 2000.8246ms
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
warn: Polly[0]
Resilience event occurred. EventName: 'OnRetry', Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error'
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
[Resilience] Retrying after failure. Attempt: 0...
--- Service Call Attempt 2 ---
[Service] Fails with HTTP 500.
warn: Polly[3]
Execution attempt. Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error', Handled: 'True', Attempt: '1', Execution Time: 317.4964ms
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
warn: Polly[0]
Resilience event occurred. EventName: 'OnRetry', Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error'
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
[Resilience] Retrying after failure. Attempt: 1...
--- Service Call Attempt 3 ---
[Service] Fails with HTTP 500.
fail: Polly[0]
Resilience event occurred. EventName: 'OnCircuitOpened', Source: 'CustomPipeline/(null)/CircuitBreaker', Operation Key: '', Result: 'Simulated 500 Internal Server Error'
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
[Circuit] State: OPEN (Blocking requests for 5s!).
warn: Polly[3]
Execution attempt. Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error', Handled: 'True', Attempt: '2', Execution Time: 1873.8853ms
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
warn: Polly[0]
Resilience event occurred. EventName: 'OnRetry', Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'Simulated 500 Internal Server Error'
System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
[Resilience] Retrying after failure. Attempt: 2...
info: Polly[3]
Execution attempt. Source: 'CustomPipeline/(null)/Retry', Operation Key: '', Result: 'The circuit is now open and is not allowing calls.', Handled: 'False', Attempt: '3', Execution Time: 0.6758ms
Polly.CircuitBreaker.BrokenCircuitException: The circuit is now open and is not allowing calls.
---> System.Net.Http.HttpRequestException: Simulated 500 Internal Server Error
at resilience_architecture_dot_net_core_demo.TestAPIService.GetDataAsync() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\TestAPIService.cs:line 21
at Program.<>c__DisplayClass0_0.<<Main>b__0>d.MoveNext() in D:\Avinash Joshi\My Projects\resilience-architecture-dot-net-core-demo\Program.cs:line 28
--- End of stack trace from previous location ---
at Polly.ResiliencePipeline.<>c__10`1.<<ExecuteAsync>b__10_0>d.MoveNext()
--- End of inner exception stack trace ---
[Application Error] Operation failed after all retries and circuit breaker logic: The circuit is now open and is not allowing calls.
Conclusion
In conclusion, this three-part implementation beautifully demonstrates how resilience architecture in .NET Core transforms fragile systems into robust, self-healing applications. By simulating real-world failures through TestAPIService, configuring intelligent fault-handling mechanisms in ResilienceServiceCollectionExtension, and executing protected calls in Program.cs, the solution showcases how patterns like Retry and Circuit Breaker work together to ensure stability, reliability, and graceful recovery from transient faults. This design not only prevents cascading failures but also strengthens overall system health — embodying the core principle of resilience: to anticipate failure, absorb its impact, and continue delivering consistent performance even in the face of adversity.
To view or download the source code click here
Thanks for reading








