.NET Core  

Build Robust Middleware in .NET: Retry and Circuit Breaker with Polly v8

Introduction

In today's distributed systems and microservices architecture, applications frequently rely on external APIs, databases, and services to function. These dependencies are prone to failures due to network issues, high traffic, or transient errors. As developers, we must design our applications to handle such failures gracefully. That's where resilience patterns come in.

In this blog, we will dive into building middleware for Retry and Circuit Breaker using the latest Polly v8, integrated with Microsoft.Extensions.Resilience.

What is the Circuit Breaker Pattern and When to Use It?

The Circuit Breaker pattern is a design pattern used in modern applications to prevent calling a service or a function that's likely to fail. Think of it like an electric circuit breaker at home - if there is an overload, the circuit trips and stops the flow of electricity to prevent damage. Similarly, in software, the circuit breaker stops calls to a failing service temporarily to avoid overwhelming it further.

When Should You Use It?

  • When an external service is known to fail frequently.
  • When you want to fail fast instead of waiting for timeouts.
  • To prevent cascading failures in microservices.
  • To improve overall system responsiveness and stability.

Circuit Breaker Flow

Circuit breaker

What is Polly?

Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback.

Polly has been widely adopted in the .NET community, and with Polly v8, Microsoft has introduced deep integration into the Microsoft.Extensions ecosystem through the new Microsoft.Extensions.Resilience package.

For more details, please visit Polly's GitHub page.

Polly with Microsoft.Extensions.Resilience

In Polly v8, resilience is more declarative and integrated into .NET’s dependency injection and configuration model. This makes it easier to build policies that are environment-aware and configurable.

Let’s start building our retry and circuit breaker middleware using the latest packages.

Step 1. Set Up Your Project

Create a new web API project in Visual Studio, or if you are using Visual Studio Code, you can execute the following commands.

mkdir PollyResilienceApp
cd PollyResilienceApp
dotnet new PollyV8RetryDemo

Post that adds below the NuGet package

# In case of Visual Studio Code
dotnet add package Microsoft.Extensions.Resilience

# In case of Visual Studio Package Manager Console
NuGet\Install-Package Microsoft.Extensions.Resilience

Step 2. Configure Resilience Policies in the Program.cs

Open the Program.cs file and add the required namespace and Poly service to place it in the middleware. Below is the snippet of Program.cs file with service.

// namespaces
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Resilience;

var builder = WebApplication.CreateBuilder(args);

// add service
builder.Services.AddResiliencePipeline("standard-pipeline", pipelineBuilder =>
{
    // add retry configurations 
    pipelineBuilder.AddRetry(new RetryStrategyOptions
    {
        MaxRetryAttempts = 3,  // -> this should be from appSettings.json
        Delay = TimeSpan.FromMilliseconds(500), // -> this should be from appSettings.json
        BackoffType = DelayBackoffType.Exponential
    });

    // set the circuit breaker pattern
    // configuration numbers should be driven from appSettings.json
    pipelineBuilder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
    {
        FailureRatio = 0.5,  // -> this should be from appSettings.json
        SamplingDuration = TimeSpan.FromSeconds(30), 
        MinimumThroughput = 10,
        BreakDuration = TimeSpan.FromSeconds(15)
    });
});

var app = builder.Build();

app.MapControllers();

app.Run();

Step 3. Inject and Use the Policy in a Controller

Once you set the service in the program.cs, it is time to inject the same in the Controllers. Below is an example of a sample controller where ResiliencePipelineProvider has been injected to execute

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Resilience;

[ApiController]
[Route("api/[controller]")]
public class MyAPIController : ControllerBase
{
    private readonly ResiliencePipelineProvider<string> _pipelineProvider;

    public MyAPIController(ResiliencePipelineProvider<string> pipelineProvider)
    {
        _pipelineProvider = pipelineProvider;
    }

    [HttpGet("execute")]
    public async Task<IActionResult> Execute_v1()
    {
        var pipeline = _pipelineProvider.GetPipeline("standard-pipeline");

        try
        {
            var result = await pipeline.ExecuteAsync(async token =>
            {
                // Actual operations goes here
                // For test, can place an sleep command
                // await Task.Delay(100);
                throw new HttpRequestException("Failure");
            });

            return Ok(result);
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Operation failed: {ex.Message}");
        }
    }
}

How Polly Helps Achieve the Circuit Breaker Pattern

Polly allows you to plug in the circuit breaker logic without cluttering your business code. The resilience pipeline acts as a middleware that surrounds your code, monitoring failures, retries, and triggering breaks when needed.

With Polly, you get:

  • Observability: You can plug in logging and metrics easily.
  • Composability: You can combine Retry + Circuit Breaker + Timeout.
  • Declarative configuration: Adjust thresholds without touching the logic.

The diagram below explains how the request is flowing.

User request through API

While all the retry actions are executing by Polly, logs can be done through the Logging Service. To enable the same, add the following code snippet in Program.cs. Appenders can be used based on the project needs.

builder.Services.AddLogging();

Conclusion

By integrating Polly v8 with Microsoft.Extensions.Resilience, you can build reliable, maintainable, and production-ready applications in .NET. Using the Retry and Circuit Breaker patterns, you shield your applications from transient errors and cascading failures.

If you’re building services at scale or working in a microservices environment, implementing these patterns is not just a good-to-have but a necessity.