How To Use Polly In C#: Easily Handle Faults And Retries

In this article, we'll explore how to use Polly in C# and how it can help handle faults and retries with ease. Polly, a popular NuGet package, provides a powerful set of tools and patterns that can greatly simplify fault handling and retry mechanisms in our code.

By leveraging Polly in our code, we can handle various types of faults, such as network errors or database connection failures. We can then implement intelligent retry strategies to ensure that our application doesn't blow up when one of these inevitably has an issue (Remember: Murphy's Law). Polly offers a flexible and configurable way of dealing with these scenarios, whether we need to handle transient faults or simply retry a failed operation.

I'll show you 3 interesting code examples that showcase different use cases of Polly in C# -- so let's get right to it!


What's In This Article: How to use Polly in C#

Remember to check out these platforms:

// FIXME: social media icons coming back soon!


1 - Handling Faults with Polly in C#

It's important to handle faults effectively when building robust and reliable applications. Fault handling refers to handling errors, exceptions, and failures that can occur during the execution of a program -- and we're going to use Polly in C# to help us out here.

To demonstrate how Polly can handle a connectivity issue, let's consider a scenario where our application needs to make a network request to an external API. We want to ensure that the request is retried automatically in case of any connection-related faults, such as temporary network outages or intermittent failures.

Let's see how to use Polly in C# to handle a connectivity issue in a network request:

using Polly;
using Polly.Retry;

var handler = new NetworkRequestHandler();
var response = await handler.MakeNetworkRequestAsync("https://www.devleader.ca/sitemap.xml");
Console.WriteLine($"Response status code: {response.StatusCode}");

public class NetworkRequestHandler
{
    private readonly HttpClient _httpClient;
    private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy;

    public NetworkRequestHandler()
    {
        _httpClient = new HttpClient();

        // Define a retry policy: retry on HttpRequestException, 3 retries with exponential backoff
        _retryPolicy = Policy
            .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .Or<HttpRequestException>()
            .WaitAndRetryAsync(3, retryAttempt =>
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryAttempt, context) =>
                {
                    Console.WriteLine($"Request failed with {outcome.Exception?.Message}. Waiting {timespan} before next retry. Retry attempt {retryAttempt}.");
                });
    }

    public async Task<HttpResponseMessage> MakeNetworkRequestAsync(string requestUri)
    {
        return await _retryPolicy.ExecuteAsync(async () =>
        {
            Console.WriteLine($"Making request to {requestUri}");
            var response = await _httpClient.GetAsync(requestUri);
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"Request failed with status code: {response.StatusCode}");
                throw new HttpRequestException($"Request to {requestUri} failed with status code {response.StatusCode}");
            }
            return response;
        });
    }
}

In the code example above, we define a NetworkRequestHandler class that encapsulates the logic for making a network request using the HttpClient class from the .NET framework. We initialize a AsyncRetryPolicy from Polly with a fault condition of type HttpRequestException. This means that the policy will be triggered whenever an HttpRequestException is thrown.

The WaitAndRetry method is used to specify the number of retry attempts and the time delay between each retry. In this example, we retry the request 3 times, with exponentially increasing delays using the formula TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)).

In the MakeNetworkRequest method, we use the Polly policy to execute the network request by calling the Execute method on the policy. If a HttpRequestException occurs during the execution, Polly will automatically retry the request according to the defined policy.


2 - Retry Mechanisms with Polly in C#

In software engineering, it is common to encounter scenarios where a particular operation may fail temporarily due to network issues, resource limitations, or other transient faults. In such cases, retry mechanisms are implemented to automatically retry the failed operation for a certain number of times until it succeeds or reaches a maximum retry count. This helps improve the reliability and robustness of the software application.

Simplifying Retry Mechanisms with Polly in C#:

When it comes to implementing retry mechanisms in C#, the NuGet package Polly comes to the rescue. Polly is a powerful library that provides an easy and elegant way to handle faults and retries in your code. It simplifies the process of implementing retries by encapsulating the complex retry logic into a set of reusable policies.

Using Polly to Automatically Retry a Failing Database Operation:

Let's consider a scenario where we need to perform a database operation, such as inserting a record into a table, but there might be temporary connectivity issues or resource constraints that could cause the operation to fail. With Polly, we can easily handle such failures by applying a retry policy.

First, we need to install the Polly NuGet package by adding the following line to the dependencies section of our project file (keeping in mind that the version you want to grab is likely newer!):

<ItemGroup>
    <PackageReference Include="Polly" Version="8.3.0" />
</ItemGroup>

Once the package is installed, we can begin using Polly to implement the retry mechanism. Here's an example code snippet that demonstrates how to use Polly to automatically retry a failing database operation:

using Polly;

using System.Data.SqlClient;

async Task<bool> InsertRecordAsync(
    Record record,
    CancellationToken cancellationToken)
{
    int maxRetryCount = 3;
    var connectionString = "// TODO: configure this elsewhere...";

    var retryPolicy = Policy
        .Handle<SqlException>() // Specify the type of exception to handle
        .WaitAndRetryAsync(maxRetryCount, retryAttempt =>
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // Exponential backoff strategy

    return await retryPolicy.ExecuteAsync(async ct =>
    {
        using var connection = new SqlConnection(connectionString);
        await connection.OpenAsync();

        using var command = connection.CreateCommand();
        command.CommandText = "INSERT INTO Records (Id, Name) VALUES (@Id, @Name)";
        command.Parameters.AddWithValue("@Id", record.Id);
        command.Parameters.AddWithValue("@Name", record.Name);

        try
        {
            // Perform the database operation            
            var result = await command.ExecuteNonQueryAsync(ct);
            return result == 1;
        }
        catch (SqlException)
        {
            // Rethrow the exception to let Polly handle the retry logic
            throw;
        }
    }, cancellationToken);
}

public sealed record Record(
    int Id,
    string Name);

In the above example, we define a retry policy using the Policy.Handle<SqlException>() method to specify the type of exception we want to handle, in this case, SQL exceptions. We then use the WaitAndRetryAsync method to configure the policy to retry the operation up to maxRetryCount times with an exponential backoff strategy, where each retry attempt waits for an increasing amount of time.

Inside the ExecuteAsync method, we wrap the database operation in a try-catch block. If the operation fails due to a SQL exception, we throw the exception to let Polly handle the retry logic. Polly will automatically retry the operation according to the defined policy, providing a seamless experience for handling temporary faults.


3 - Advanced Retries and Circuit Breaker Patterns with Polly in C#

Sometimes we need more advanced retry mechanisms and circuit breaker patterns to get us to those next levels of resilience in our applications. Fortunately, the Polly library in C# has us covered for these as well!

When working with external services or resources, it's common to encounter transient faults such as network issues or service unavailability. Polly allows us to define custom retry policies that dictate how many times to retry and with what delay between retries. This ensures that our application can gracefully handle temporary failures and improve the overall reliability of the system.

Customizing Retry Policies with Polly

Polly provides flexible options to customize retry policies based on specific requirements. We can define the number of retries, the backoff strategy to determine the delay between retries, and even define a predicate to selectively retry only on certain exceptions. By tailoring these policies, we can handle different scenarios effectively.

Here's an example that shows how to use Polly for implementing a retry policy with an exponential backoff strategy:

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response => response.StatusCode == HttpStatusCode.InternalServerError)
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

In this example, we define a retry policy that will retry the operation up to 3 times if an HttpRequestException or if the HttpResponseMessage has a status code of InternalServerError. The backoff strategy is exponential, with the delay between retries increasing exponentially based on the retry attempt number.

Implementing Circuit Breaker Patterns with Polly in C#

Circuit breaker patterns play an important role in preventing cascading failures and protecting systems from a flurry of repeated requests to a failing service. Polly provides built-in support for implementing circuit breaker patterns, which help in automatically managing the state of the circuit breaker and controlling the flow of requests.

To implement a circuit breaker pattern with Polly, we need to define the threshold values for failures and successes, along with the time window in which to monitor these metrics. Once the circuit breaker trips, further requests are short-circuited, preventing unnecessary calls to the failing service.

Here's an example of how to use Polly for implementing a circuit breaker pattern:

var circuitBreakerPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response => response.StatusCode == HttpStatusCode.InternalServerError)
    .CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));

In this example, the circuit breaker policy is configured to trip open if there are 3 consecutive failures within a 30-second window. Once the circuit breaker is open, subsequent requests will be automatically rejected, preventing further calls until a specified recovery period has passed.


Now You Know How to Use Polly in C#!

You've done it! You have 3 working code examples to help you understand how to use Polly in C#. By Polly, you can handle faults and retries with ease, improving the reliability and resilience of your applications. In this article, we explored three different use cases of Polly, covering fault handling, basic retries, and advanced retry patterns like circuit breakers.

By incorporating Polly into your projects, you can enhance the stability and responsiveness of your software. By using policies to specify retry counts, durations, and other configurable parameters, you can control the behavior and tolerance of your application in the face of failures. If you found this useful and you're looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube!