Retry
- In Web API, many endpoints depend on other services. If these services are temporarily slow or unavailable, retrying automatically can help the application avoid unnecessary errors.
- Retry means trying the same operation again when it fails due to a problem that is likely to be temporary. Instead of giving up on the very first failure, the application waits for a short time and then makes another attempt
How it works
- First attempt fails
- Wait for a short time
- Try again
- If needed, try a few more times
- If it still fails, then return the failure
Transient Failures
- Retry helps the application handle Transient Failures automatically.
- A transient failure is a short-lived failure that may disappear if the same request is tried again after a small delay.
When Should You Retry?
- Retry should be used only when the failure is likely to be temporary.
- Good candidates for Retry include:
- Temporary network glitches
- Temporary DNS resolution issues
- 408 Request Timeout
- 429 Too Many Requests
- 5xx server errors, such as 500, 502, 503, and 504
- Short service overloads
- Temporary downstream service restarts
When Should You Not Retry?
Bad candidates for Retry include:
- 400 Bad Request
- Validation errors
- Business rule violations
- Authentication failures
- Authorization failures
- Incorrect API key
- Invalid route or endpoint
- Duplicate submission scenarios
POST Requests
- Retries can be risky for non-idempotent operations such as POST requests.
- A non-idempotent operation means repeating the same request may create a different result each time.
- For example:
- Creating duplicate orders
- Creating duplicate payments
- Inserting the same record twice
- Retrying a GET is usually safer.
- Retrying a POST needs more care.
Polly
- Polly is a popular .NET library used to handle transient faults in a clean and structured way.
- Instead of writing manual retry logic again and again in different parts of the application, Polly allows us to define resilience strategies, such as Retry, in a consistent and reusable manner.
- With Polly, we can:
- Define how many times to retry
- Define how long to wait between retries
- Decide which failures should be retried
- Centralize resilience behavior
- Keep business code clean and readable
Implementation
- Create new web API project
- Install package
Microsoft.Extensions.Http.Resilience
- Create new controller and add code
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory;
public DataController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet("fetch")]
public async Task<IActionResult> FetchData()
{
var client = _httpClientFactory.CreateClient("ExternalApiService");
try
{
// Make the HTTP request
var response = await client.GetAsync("todos");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return Ok(content);
}
catch (HttpRequestException ex)
{
// Standard network or HTTP status failure
return StatusCode(StatusCodes.Status502BadGateway,
$"Error calling external service: {ex.Message}");
}
}
}
- Update program.cs
// Add services to the container.
// Register a named HttpClient
builder.Services.AddHttpClient("ExternalApiService", client =>
{
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
})
.AddResilienceHandler("retry-pipeline", (pipelineBuilder, context) =>
{
// Resolve the standard standard ILogger using the context service provider
var logger = context.ServiceProvider.GetRequiredService<ILogger<Program>>();
// 1. RETRY POLICY (Outer Layer)
pipelineBuilder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3, // Retry up to 3 times
Delay = TimeSpan.FromSeconds(2), // Wait 2 seconds between retries
BackoffType = DelayBackoffType.Exponential, // Wait longer after each failure (2s, 4s, 8s)
UseJitter = true, // Add a slight random delay variation to protect servers
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.Handle<TimeoutException>()
.HandleResult(response => !response.IsSuccessStatusCode),
// Custom logging hook for retries
OnRetry = outcome =>
{
logger.LogWarning("⚠️ [Retry] Attempt #{Attempt} failed. Reason: {Reason}. Waiting before next retry...",
outcome.AttemptNumber + 1, // AttemptNumber is 0-indexed in Polly v8
outcome.Outcome.Exception?.Message ?? $"HTTP {outcome.Outcome.Result?.StatusCode}");
return default; // Returns a completed ValueTask
}
});
});