Reliable API Calls in .NET Worker Service with Polly

When building background services in .NET, it's common to integrate with external APIs for tasks such as data synchronization, notifications, payment processing, or fetching third-party information. However, external services are not always reliable. Network interruptions, temporary server issues, rate limiting, and other transient failures can cause API requests to fail unexpectedly.

If these failures are not handled properly, a .NET Worker Service may stop processing data, miss important operations, or generate unnecessary errors. To build resilient applications, it's essential to implement strategies that can gracefully handle temporary faults and recover automatically.

This is where Polly comes in. Polly is a popular .NET resilience library that provides fault-handling capabilities such as retries, circuit breakers, timeouts, and fallback policies. By integrating Polly with HttpClient in a .NET Worker Service, you can significantly improve the reliability of API communication and reduce the impact of transient failures.

In this article, you'll learn how to use Polly in a .NET Worker Service to implement retry policies for API calls, improve application resilience, and ensure that temporary issues don't disrupt your background processing workflows.

Reliable API Calls in .NET Worker Service with Polly

A Worker Service often runs continuously in the background without user intervention. If an external API becomes temporarily unavailable, a failed request can interrupt business processes or cause data loss. Instead of failing immediately, applications should retry operations when the failure is likely to be temporary.

Why API Calls Fail in Worker Services

A .NET Worker Service often relies on external APIs to fetch data, send notifications, process payments, or perform other background operations. While these integrations are essential, they also introduce dependencies on systems outside your control

API requests can fail for various reasons, many of which are temporary and resolve on their own after a short period. Common causes include network connectivity issues, temporary API outages, DNS resolution failures, rate limiting responses (HTTP 429), server-side errors (HTTP 5xx), and request timeouts.

Without proper handling, these transient failures can cause background jobs to fail, delay critical processing, or create inconsistent application behavior. Implementing resilience strategies such as retries helps Worker Services recover from these temporary issues and continue operating reliably.

Polly Strategies in Worker Services for API Calls

For API calls in a .NET Worker Service, not every Polly strategy is equally important. These are the most useful ones, ordered by how commonly they're used in production:
  1. Retry (Most Common): Retries failed requests when the failure is likely temporary. For example an external API returns a 503 Service Unavailable error. Instead of failing immediately, Polly retries the request after a short delay.
  2. Circuit Breaker: Stops sending requests to an API that is consistently failing. Example, If an API fails 10 consecutive times, the circuit breaker opens and blocks further requests for a specified period.
  3. Timeout: Cancels requests that take too long to complete. If an API doesn't respond within 10 seconds, Polly cancels the request and handles the timeout gracefully.
  4. Rate Limiter: Controls how frequently requests are sent. Your Worker Service processes thousands of records, but the API allows only 100 requests per minute.
  5. Fallback: Provides an alternative action when all retries fail. If the API remains unavailable after all retry attempts, save the request to a queue for later processing.
  6. Bulkhead Isolation: Limits the number of concurrent requests. Restrict API calls to 20 concurrent requests even if the Worker Service is processing hundreds of tasks.

In this article you'll focus on Retry Policy, Timeout and Circuit Breaker Strateges which is the most common requirement for API calls.

Implementing a Timeout, Retry, Circuit Breaker Strategies with Polly

Modern distributed applications frequently interact with external services such as APIs, databases, and message brokers. These dependencies can experience transient failures, latency spikes, or complete outages. To build resilient applications, developers need mechanisms that can gracefully handle such failures.

Polly is a popular .NET resilience and transient-fault-handling library that provides policies such as Retry, Timeout, Circuit Breaker, Fallback, and Bulkhead Isolation. Here we'll explore how to implement Timeout, Retry, and Circuit Breaker strategies using Polly.

Install Required Packages

Before implementing timeout, retry, and circuit breaker strategies in a .NET Worker Service, you need to install the required NuGet packages. The Polly package is the core resilience library that provides fault-handling capabilities such as retries, timeouts, circuit breakers, fallback policies, and bulkhead isolation. These features help applications handle transient failures and improve reliability when communicating with external services.

The Microsoft.Extensions.Http.Polly package integrates Polly with the .NET HttpClientFactory, allowing resilience policies to be applied automatically to all HTTP requests made through a configured HttpClient.

Instead of manually wrapping each HTTP call with Polly policies, this integration enables developers to register policies centrally during dependency injection configuration, resulting in cleaner, more maintainable code. Together, these packages provide a robust framework for building resilient Worker Services that can gracefully handle network issues, temporary service outages, and slow responses.

Create a Typed HTTP Client

A typed HTTP client is a design pattern in .NET that encapsulates all communication with a specific external API inside a dedicated class. Instead of injecting and using HttpClient directly throughout the application, a typed client receives an HttpClient instance through dependency injection and exposes methods that represent the operations of the target service.

This approach improves code organization, readability, and maintainability by centralizing API-related logic, such as request creation, response handling, and error management, in one place. It also works seamlessly with IHttpClientFactory, allowing developers to configure settings such as the base URL, default headers, and Polly resilience policies (retry, timeout, circuit breaker) when registering the client.

In a Worker Service, a typed HTTP client acts as an abstraction layer between the worker and the external service, making the code easier to test, reuse, and maintain while ensuring consistent HTTP communication practices across the application.


public class WeatherApiClient
{
  private readonly HttpClient _httpClient;
  public WeatherApiClient(HttpClient httpClient)
  {
    _httpClient = httpClient;
  }
  public async Task<string> GetWeatherAsync()
  {
    var response = await _httpClient.GetAsync("/weather");
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
  }
}

Implementing a Retry Strategy


static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
  return HttpPolicyExtensions
  .HandleTransientHttpError()
  .WaitAndRetryAsync(
  retryCount: 3,
  sleepDurationProvider: retryAttempt =>
  TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
  onRetry: (outcome, delay, retryAttempt, context) =>
  {
    Console.WriteLine(
    $"Retry {retryAttempt} after {delay.TotalSeconds}s");
    });
  }

How it Works

The GetRetryPolicy() method creates a Polly retry policy that automatically retries failed HTTP requests when transient errors occur. A transient error is a temporary failure, such as a network interruption, a timeout, or server-side errors (HTTP 5xx responses), which may succeed if the request is attempted again after a short delay.

The HandleTransientHttpError() method tells Polly to monitor for common transient HTTP failures, including HttpRequestException, HTTP 5xx server errors, and HTTP 408 (Request Timeout) responses. When one of these errors occurs, Polly activates the retry policy instead of immediately returning the failure to the application.

The WaitAndRetryAsync() method configures the retry behavior. The retryCount: 3 parameter specifies that Polly should retry the request up to three times after the initial failure. The sleepDurationProvider determines how long Polly waits before each retry. In this example, an exponential backoff strategy is used:


TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))

This calculates the delay as:
  • Retry 1 → 2¹ = 2 seconds
  • Retry 2 → 2² = 4 seconds
  • Retry 3 → 2³ = 8 seconds

Using exponential backoff prevents the application from repeatedly sending requests too quickly to a struggling service and gives the external system time to recover.

The onRetry callback executes before each retry attempt. It provides information about the failed request (outcome), the delay before the next retry (delay), the current retry number (retryAttempt), and the execution context (context). In this example, it logs a message indicating which retry is being performed and how long Polly will wait before trying again.

For example, if an API call returns an HTTP 500 error, Polly will:
  1. Detect the transient failure.
  2. Wait 2 seconds and retry.
  3. If it fails again, wait 4 seconds and retry.
  4. If it fails a third time, wait 8 seconds and retry.
  5. If the request still fails after the third retry, Polly stops retrying and returns the exception or failed response to the application.

This approach improves application resilience by automatically recovering from temporary failures without requiring manual retry logic in the code.

Create a Circuit Breaker Strategy


static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
  return HttpPolicyExtensions
  .HandleTransientHttpError()
  .CircuitBreakerAsync(
  handledEventsAllowedBeforeBreaking: 5,
  durationOfBreak: TimeSpan.FromSeconds(30),
  onBreak: (result, duration) =>
  {
    Console.WriteLine(
    $"Circuit opened for {duration.TotalSeconds}s");
    },
    onReset: () =>
    {
      Console.WriteLine("Circuit reset");
      });
    }

The GetCircuitBreakerPolicy() method creates a Polly circuit breaker policy that protects your application from repeatedly calling an external service that is failing. It works by monitoring transient HTTP errors such as server errors (5xx responses), timeouts (408), and network-related exceptions. The policy allows up to 5 consecutive failed requests, and if this threshold is reached, it “opens” the circuit, meaning all further requests are immediately blocked without even reaching the external service. This prevents wasting resources and avoids putting additional load on a failing system. Once the circuit is open, it stays in that state for 30 seconds, during which no calls are allowed.

After this break period, Polly transitions to a half-open state and allows a test request to check if the external service has recovered. If the request succeeds, the circuit is closed again and normal communication resumes; if it fails, the circuit remains open for another cycle.

The onBreak callback is triggered when the circuit opens and is typically used for logging or monitoring, while the onReset callback runs when the circuit successfully closes again. Overall, this pattern improves system stability by preventing continuous failures and giving dependent services time to recover.

Implementing a Timeout Strategy


static IAsyncPolicy<HttpResponseMessage> GetTimeoutPolicy()
{
  return Policy.TimeoutAsync<HttpResponseMessage>(
  TimeSpan.FromSeconds(5),
  TimeoutStrategy.Pessimistic);
}

The GetTimeoutPolicy() method creates a Polly timeout policy that ensures any HTTP request taking longer than a specified duration is automatically aborted. In this case, the timeout is set to 5 seconds using TimeSpan.FromSeconds(5), meaning if an external service does not respond within 5 seconds, Polly will stop waiting and treat the request as failed.

The TimeoutStrategy.Pessimistic option enforces the timeout strictly by cancelling the execution even if the underlying operation does not naturally support cancellation, which is important for preventing hanging requests in a Worker Service.

When a request exceeds the allowed time, Polly throws a TimeoutRejectedException, allowing the application to handle it like any other failure (for example, through retry or circuit breaker policies).

This mechanism helps ensure that slow or unresponsive external services do not block threads or degrade system performance, keeping the Worker Service responsive and resilient.

Configure Polly Strategy In Program.cs


using Polly;
using Polly.Extensions.Http;
using Polly.Timeout;
var builder = Host.CreateApplicationBuilder(args);
//wrapping polly strategies
var policyWrap = Policy.WrapAsync(
GetRetryPolicy(),
GetCircuitBreakerPolicy(),
GetTimeoutPolicy());
//registering http client
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
  client.BaseAddress = new Uri("https://api.example.com");
  })
  .AddPolicyHandler(policyWrap);
How it works at runtime when your worker calls the API
  1. Retry policy (outer layer): Tries the request up to 3 times if it fails
  2. Circuit Breaker policy (middle layer): Stops calls if failures exceed threshold (5 failures)
  3. Timeout policy (inner layer): Cancels request if it takes longer than 5 seconds
  4. Finally: the HTTP request is executed

Why use Policy.WrapAsync

Instead of attaching policies separately:

.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy)
.AddPolicyHandler(timeoutPolicy)
You use WrapAsync when you want:
  • Full control over execution order
  • A single unified resilience pipeline
  • Easier reuse across multiple clients

Recommended Ordering

Policy.WrapAsync(
retryPolicy,
circuitBreakerPolicy,
timeoutPolicy);
Behavior:
  1. Timeout aborts slow requests.
  2. Retry handles transient failures and timeout exceptions.
  3. Circuit breaker tracks repeated failures and opens when a threshold is reached.

Use in a Worker Service


public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;
  private readonly WeatherApiClient _client;
  public Worker(
  ILogger<Worker> logger,
  WeatherApiClient client)
  {
    _logger = logger;
    _client = client;
  }
  protected override async Task ExecuteAsync(
  CancellationToken stoppingToken)
  {
    while (!stoppingToken.IsCancellationRequested)
    {
      try
      {
        var result = await _client.GetWeatherAsync();
        _logger.LogInformation(
        "Response: {Result}", result);
      }
      catch (Exception ex)
      {
        _logger.LogError(
        ex,
        "Failed to call weather service");
      }
      await Task.Delay(
      TimeSpan.FromSeconds(10),
      stoppingToken);
    }
  }
}
Best Practices
  • Use exponential backoff instead of fixed retry intervals.
  • Keep timeout values lower than your worker polling interval.
  • Log circuit breaker state changes (Open, Half-Open, Closed).
  • Avoid retrying non-transient failures (e.g., HTTP 400).
  • Consider adding jitter to retry delays to avoid retry storms.
  • Use health checks and metrics to monitor circuit breaker activity.

This combination gives your Worker Service protection against slow dependencies, temporary outages, and cascading failures while keeping resource usage under control.

Summary

Polly helps .NET Worker Services handle transient API failures gracefully. By implementing retry policies with HttpClient, you can improve reliability, reduce temporary failures, and build more resilient background processing applications. While retries are a great starting point, combining them with circuit breakers and timeout policies provides even stronger protection for production workloads.

Thanks

Kailash Chandra Behera

I am an IT professional with over 13 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies.

Previous Post Next Post

نموذج الاتصال