We're diving deeper into the world of Polly - the .NET resilience and transient-fault-handling library that does more than manage retries. This powerful tool offers various features to help you deal with everyday tasks and challenges more efficiently.

Introduction to Polly

Polly is a .NET library designed to enhance your application's resilience and easily manage transient faults. With Polly, you turn network hiccups and temporary disruptions into manageable scenarios. Let's dive into some of the powerful features Polly offers.

1. Retry Policies

Retry policies are designed to handle transient failures. For instance, if an HTTP request fails, you can use Polly to retry the request.

var retryPolicy = Policy
    .Handle<Exception>()
    .RetryAsync(
        retryCount: 3, 
        onRetry: (exception, retryCount, context) =>
        {
            _logger.Error(
                exception,
                "Retry {RetryCount} of method {TargetSite} due to {Message}",
                retryCount,
                exception.TargetSite?.Name,
                exception.Message);
        });

await retryPolicy.ExecuteAsync(action: () => DoOperation());

2. Circuit Breaker

The Circuit Breaker pattern is designed to prevent an application from continuously trying to execute a failing operation. After a set number of failed attempts, the operation is stopped.

var circuitBreakerPolicy = Policy
    .Handle<Exception>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromMinutes(1),
        onBreak: (ex, breakDelay) =>
        {
            _logger.Error($"Circuit broken! {ex.Message}");
        },
        onReset: () =>
        {
            _logger.Information("Circuit Reset!");
        });

await circuitBreakerPolicy.ExecuteAsync(() => DoOperation());

3. Timeout Policies

Timeouts are essential for application resilience. Polly allows you to specify timeout policies to prevent operations from running indefinitely.

var timeoutPolicy = Policy.TimeoutAsync(
    timeout: TimeSpan.FromSeconds(2), 
    onTimeoutAsync: (context, timespan, task) =>
    {
        _logger.Error("Execution timed out!");
        return Task.CompletedTask;
    });

await timeoutPolicy.ExecuteAsync(action: () => DoOperation());

4. Bulkhead Isolation

Bulkhead Isolation is a method of limiting the number of parallel operations that can be executed. Like a ship's bulkhead, this feature contains failures and prevents them from affecting the entire application. This ensures a fair distribution of resources, maintaining your service's responsiveness under high loads or during faults.

var bulkheadPolicy = Policy
    .BulkheadAsync(
        maxParallelization: 2,
        maxQueuingActions: 4, 
        onBulkheadRejectedAsync: context =>
        {
            _logger.Warning("Bulkhead rejected a request!");
            return Task.CompletedTask;
        });

await bulkheadPolicy.ExecuteAsync(action: () => DoOperation());

5. Fallback Policies

Fallback policies provide a default behavior when all else fails, offering a better user experience when dealing with unexpected issues.

var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<Exception>()
    .FallbackAsync(
        fallbackValue: new HttpResponseMessage(HttpStatusCode.GatewayTimeout),
        onFallbackAsync: (exception, context) =>
        {
            _logger.Error("Fallback executed!");
            return Task.CompletedTask;
        });

await fallbackPolicy.ExecuteAsync(action: () => DoOperation());

6. Policy Wrap

Policy Wrap allows you to combine multiple policies together for more robust fault handling. For instance, you can wrap Retry and Fallback policies together to ensure that a failed operation is retried, and if all retries fail, a default action is executed.

var retryPolicy = Policy
    .Handle<Exception>()
    .RetryAsync(3);

var fallbackPolicy = Policy
    .Handle<Exception>()
    .FallbackAsync(FallbackAction);

var policyWrap = Policy.WrapAsync(retryPolicy, fallbackPolicy);

await policyWrap.ExecuteAsync(() => DoOperation());

7. HTTP Client Factory

Polly can be combined with the HTTP Client Factory to make your HTTP requests more resilient.

services
    .AddHttpClient("ClientName")
    .AddTransientHttpErrorPolicy(
        p => p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

8. Monitoring with Event Listeners

Polly's Event Listeners allow you to observe the operations of your policies. This is useful for logging or monitoring metrics and gaining insights into your policies' functioning.

var policy = Policy.Handle<Exception>()
    .RetryAsync(3)
    .OnRetry((exception, retryCount) =>
    {
        _logger.Error(
            exception,
            "Retry {RetryCount} of method {TargetSite} due to {Message}",
            retryCount,
            exception.TargetSite.Name,
            exception.Message);
    });

await policy.ExecuteAsync(() => DoOperation());

Wrapping Up

That's our dive into Polly's offerings. It's more than a retry policy tool—it's an arsenal of resilience strategies for your .NET applications. Polly has got you covered, whether it's handling transient faults, maintaining resilience in high-load scenarios, or improving user experience during unexpected failures.