ASP.NET Core Web API

Last Updated: 9/8/2026

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
        }
    });
});