When building modern .NET microservices, out-of-process HTTP communication is one of the most common failure points. Network glitches, service deployments, and high latency can easily cascade across your system, causing thread starvation and widespread outages.
Combining Refit (a strongly-typed REST client library) with Polly v8 (the standard resilience framework in .NET 8+) offers a clean, production-grade pattern to protect your microservices against cascading failures using the Circuit Breaker pattern.
The Problem: Cascading HTTP Failures
Imagine an Order Microservice calling a Payment Microservice via HTTP. If the Payment service goes down or experiences a database deadlock, incoming requests to the Order service will hang while waiting for responses.
Without a circuit breaker:
Every new order request allocates a thread to call the failing Payment API.
Threads quickly pile up, consuming CPU and RAM.
The Order service eventually runs out of thread-pool resources, crashing entirely—even for endpoints that don't depend on the Payment service.
The Solution: The Circuit Breaker Pattern
A Circuit Breaker monitors outgoing HTTP traffic and automatically short-circuits requests when a dependency fails repeatedly:
Closed State (Normal): Requests flow straight through to the downstream service.
Open State (Short-Circuited): When the failure threshold is breached (e.g., >50% failure rate), calls fail instantly locally with a
BrokenCircuitException. No network calls are made, preserving local CPU resources and preventing thread starvation.Half-Open State (Trial): After a cooldown period, trial requests are allowed through to test if the downstream service has recovered.
Step-by-step Implementation
1. Define the Refit Client Interface
Refit eliminates boilerplate HTTP code by mapping C# interfaces directly to HTTP endpoints:
using Refit;
public record PaymentRequest(string OrderId, decimal Amount);
public record PaymentResponse(string TransactionId, string Status);
public interface IPaymentApi
{
[Post("/api/v1/payments")]
Task<PaymentResponse> ProcessPaymentAsync([Body] PaymentRequest request);
}
2. Configure Refit with Polly Resilience Pipelines
In .NET 8+, the Microsoft.Extensions.Http.Resilience package allows you to attach Polly v8 pipelines directly to Refit clients during Dependency Injection setup using AddResilienceHandler.
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly;
using Polly.CircuitBreaker;
using Refit;
var builder = Host.CreateApplicationBuilder(args);
// Register the Refit Client with a Circuit Breaker Pipeline
builder.Services.AddRefitClient<IPaymentApi>()
.ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri("https://api.paymentservice.com");
client.Timeout = TimeSpan.FromSeconds(5);
})
.AddResilienceHandler("RefitPaymentCircuitBreaker", resilienceBuilder =>
{
resilienceBuilder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
// Open the circuit if 50% or more calls fail in the sampling window
FailureRatio = 0.5,
// Minimum throughput required in the window before rules apply
MinimumThroughput = 5,
// Time window over which failure rate is calculated
SamplingDuration = TimeSpan.FromSeconds(10),
// Time the circuit stays OPEN before entering HALF-OPEN state
BreakDuration = TimeSpan.FromSeconds(15),
// Define what errors trigger the circuit breaker
ShouldHandle = new HttpResiliencePredicateBuilder()
.HandleStatusCode(HttpStatusCode.InternalServerError)
.HandleStatusCode(HttpStatusCode.ServiceUnavailable)
.HandleStatusCode(HttpStatusCode.RequestTimeout)
.Build(),
// Monitoring and Observability Callbacks
OnOpened = args =>
{
Console.WriteLine($"[CIRCUIT OPEN] Payment API unreachable. Short-circuiting traffic for {args.BreakDuration.TotalSeconds}s.");
return ValueTask.CompletedTask;
},
OnClosed = args =>
{
Console.WriteLine("[CIRCUIT CLOSED] Payment API recovered. Resuming normal operations.");
return ValueTask.CompletedTask;
},
OnHalfOpened = args =>
{
Console.WriteLine("[CIRCUIT HALF-OPEN] Sending trial requests to verify recovery...");
return ValueTask.CompletedTask;
}
});
});
3. Consume the Refit Client Safely with Fallback Handling
When the circuit is Open, calls to IPaymentApi throw a BrokenCircuitException. Handle this gracefully in your application logic to provide fallback behavior (e.g., queueing the order for processing later):
public class OrderService
{
private readonly IPaymentApi _paymentApi;
public OrderService(IPaymentApi paymentApi)
{
_paymentApi = paymentApi;
}
public async Task<bool> CheckoutAsync(string orderId, decimal amount)
{
try
{
var response = await _paymentApi.ProcessPaymentAsync(new PaymentRequest(orderId, amount));
return response.Status == "APPROVED";
}
catch (BrokenCircuitException)
{
// Circuit is OPEN - Service is down, short-circuit locally
Console.WriteLine($"Order {orderId}: Payment service unavailable. Queueing for offline execution.");
await SaveToOfflineQueueAsync(orderId, amount);
return false;
}
catch (ApiException ex)
{
// Handle HTTP response error codes (4xx, 5xx)
Console.WriteLine($"Order {orderId}: API returned error: {ex.StatusCode}");
return false;
}
}
private Task SaveToOfflineQueueAsync(string orderId, decimal amount)
{
// Outbox/Queue logic here
return Task.CompletedTask;
}
}
Best Practices for Refit + Circuit Breakers
Combine Retry with Circuit Breaker: Layer a Retry policy with exponential backoff and jitter inside the Circuit Breaker. Let short transient glitches self-heal via retries before blowing the breaker.
Set Sensible Timeouts: Always configure
HttpClient.Timeoutor use Polly'sAttemptTimeout. Uncapped HTTP requests will hold connections open indefinitely, preventing the circuit breaker from evaluating metrics promptly.Log & Export Metrics: Wire up the
OnOpenedandOnCloseddelegates to your logging framework (e.g., Serilog, OpenTelemetry) to trigger alerts when critical dependencies fail.

Jasen FiciPosted Aug 17, 2026, 11:38 AM
We picked this up for DotNetNews here: https://dotnetnews.co/archive/the-net-news-daily-issue-520/