PollyAzureKeyVault

Polly v8 resilience for Azure.Security.KeyVault.Secrets — add retry, timeout, and circuit-breaker to any Azure Key Vault operation in two lines.
var secretClient = new SecretClient(new Uri("https://my-vault.vault.azure.net/"), credential);
var resilient = secretClient.WithPolly(pipeline => pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = KeyVaultTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(10)));
var secret = await resilient.GetSecretAsync("my-secret");
Why PollyAzureKeyVault?
Azure Key Vault is a hard dependency for most production apps — connection failures at startup or runtime cause outages. This library adds Polly v8 resilience without boilerplate:
| Problem |
Solution |
| HTTP 429 throttling (Key Vault has strict rate limits) |
Caught by KeyVaultTransientErrors.IsTransient |
| HTTP 503 during Key Vault maintenance / regional failover |
Caught by KeyVaultTransientErrors.IsTransient |
| HTTP 504 gateway timeout |
Caught by KeyVaultTransientErrors.IsTransient |
HttpRequestException network failure |
Caught by KeyVaultTransientErrors.IsTransient |
TaskCanceledException slow response |
Caught by KeyVaultTransientErrors.IsTransient |
| Cascading failures propagating to the rest of the app |
Wrap with AddCircuitBreaker |
Installation
dotnet add package PollyAzureKeyVault
dotnet add package Polly.Core
Quick-start
1. Manual wiring
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Polly;
using Polly.Retry;
var credential = new DefaultAzureCredential();
var client = new SecretClient(new Uri("https://my-vault.vault.azure.net/"), credential);
var resilient = client.WithPolly(p => p
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = KeyVaultTransientErrors.IsTransient,
}));
// Typed convenience methods
var secret = await resilient.GetSecretAsync("connection-string");
Console.WriteLine(secret.Value.Value);
2. Dependency injection
// Program.cs / Startup.cs
builder.Services.AddSingleton(new SecretClient(
new Uri("https://my-vault.vault.azure.net/"),
new DefaultAzureCredential()));
builder.Services.AddPollyAzureKeyVault(pipeline => pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = KeyVaultTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(10)));
// Inject ResilientSecretClient into your services
public class SecretsService(ResilientSecretClient client)
{
public async Task<string> GetConnectionStringAsync(CancellationToken ct) =>
(await client.GetSecretAsync("db-connection-string", cancellationToken: ct)).Value.Value;
}
3. With a URI shortcut (registers SecretClient automatically)
builder.Services.AddPollyAzureKeyVault(
new Uri("https://my-vault.vault.azure.net/"),
pipeline => pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5,
ShouldHandle = KeyVaultTransientErrors.IsTransient,
}));
Transient error reference
// Use in any Polly strategy:
ShouldHandle = KeyVaultTransientErrors.IsTransient
| Condition |
Why it's transient |
RequestFailedException (HTTP 429) |
Key Vault rate limit — back off and retry |
RequestFailedException (HTTP 503) |
Vault maintenance or regional failover |
RequestFailedException (HTTP 504) |
Proxy or load balancer timed out |
HttpRequestException |
Network failure |
TaskCanceledException |
Request timed out in transit |
Handling only 429s with a longer back-off
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle<RequestFailedException>(ex => ex.Status == 429),
MaxRetryAttempts = 5,
Delay = TimeSpan.FromSeconds(10),
BackoffType = DelayBackoffType.Exponential,
})
Advanced pipelines
Full production pipeline
client.WithPolly(p => p
.AddTimeout(TimeSpan.FromSeconds(30))
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 4,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = KeyVaultTransientErrors.IsTransient,
OnRetry = args =>
{
logger.LogWarning("Key Vault retry {Attempt} — {Exception}",
args.AttemptNumber, args.Outcome.Exception?.Message);
return ValueTask.CompletedTask;
},
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = KeyVaultTransientErrors.IsTransient,
}));
Arbitrary operations via ExecuteAsync
// Use ExecuteAsync<T> for any SecretClient method not covered by typed overloads
var props = await resilient.ExecuteAsync(
(c, ct) => c.GetDeletedSecretAsync("old-secret", ct));
API reference
ResilientSecretClient
| Member |
Description |
Inner |
The underlying SecretClient |
GetSecretAsync(name, version?, ct) |
Retrieves a secret through the pipeline |
SetSecretAsync(name, value, ct) |
Creates or updates a secret through the pipeline |
StartDeleteSecretAsync(name, ct) |
Begins deletion through the pipeline |
ExecuteAsync<T>(operation, ct) |
Runs any SecretClient operation through the pipeline |
KeyVaultTransientErrors
| Member |
Description |
IsTransient |
PredicateBuilder for 429/503/504 RequestFailedException, HttpRequestException, TaskCanceledException |
StatusCodes |
IReadOnlySet<int> — {429, 503, 504} |
Extension methods
| Method |
Description |
client.WithPolly(pipeline) |
Wraps a SecretClient with a pre-built ResiliencePipeline |
client.WithPolly(configure) |
Builds a pipeline inline and wraps the client |
DI extensions
| Method |
Description |
services.AddPollyAzureKeyVault(configure) |
Registers ResiliencePipeline + ResilientSecretClient (requires SecretClient already in DI) |
services.AddPollyAzureKeyVault(uri, configure) |
Registers SecretClient with DefaultAzureCredential, then pipeline + resilient client |
Target frameworks
| Framework |
Supported |
| .NET 6 |
✅ |
| .NET 8 |
✅ |
| .NET 9 |
✅ |
| Package |
Downloads |
Description |
| PollyHealthChecks |
 |
ASP.NET Core health checks for Polly v8 circuit breakers — expose circuit-breaker state (Closed, HalfOpen, Open, Isolated) as /health endpoint responses |
| PollyBackoff |
 |
Backoff delay strategies for Polly v8 resilience pipelines |
| PollyGrpc |
 |
Polly v8 resilience interceptor for gRPC |
| PollyEFCore |
 |
Polly v8 resilience pipelines for Entity Framework Core — wrap every EF Core query and SaveChanges with retry, timeout and circuit-breaker via a single AddPollyResilience() call |
| PollyRabbitMQ |
 |
Polly v8 resilience for RabbitMQ.Client v7+ — retry, circuit-breaker, and timeout for IChannel operations, with built-in RabbitMqTransientErrors predicate covering AlreadyClosedException, BrokerUnreachableException, OperationInterruptedException, and ConnectFailureException |
| PollyMailKit |
 |
Polly v8 resilience pipelines for MailKit — retry, timeout, and circuit-breaker for SmtpClient.SendAsync and any MailKit SMTP operation |
| PollyMassTransit |
 |
Polly v8 resilience pipelines for MassTransit — retry, timeout, and circuit-breaker for IBus.Publish and ISendEndpointProvider.Send |
| PollyNpgsql |
 |
Polly v8 resilience pipelines for Npgsql (PostgreSQL) — retry, timeout, and circuit-breaker for NpgsqlConnection queries and commands, plus a built-in PostgresTransientErrors predicate covering all common PostgreSQL transient SQLSTATE codes |
| PollyOpenAI |
 |
Polly v8 resilience for OpenAI and Azure OpenAI API calls |
| PollyAzureEventHub |
 |
Polly v8 resilience pipelines for Azure Event Hubs — retry, timeout, and circuit-breaker for EventHubProducerClient and EventHubConsumerClient |
| PollySignalR |
 |
Polly v8 reconnect policy for SignalR |
| PollyElasticsearch |
 |
Polly v8 resilience pipelines for Elastic.Clients.Elasticsearch 8+ — retry, timeout, and circuit-breaker for any Elasticsearch operation, plus a built-in ElasticTransientErrors predicate covering rate limiting (429), service unavailability (503), gateway timeouts (504), and connection failures |
| PollyHangfire |
 |
Polly v8 resilience pipelines for Hangfire — retry, timeout, and circuit-breaker for IBackgroundJobClient.Enqueue and Schedule |
| PollyCosmosDb |
 |
Polly v8 resilience pipelines for Azure Cosmos DB — retry, timeout, and circuit-breaker for Container operations, plus a built-in CosmosTransientErrors predicate covering rate limiting (429), timeouts (408), partition failovers (410), and service unavailability (503) |
| PollySendGrid |
 |
Polly v8 resilience pipelines for SendGrid — retry, timeout, and circuit-breaker for ISendGridClient.SendEmailAsync |
| PollyMongo |
 |
Polly v8 resilience pipelines for MongoDB.Driver — wrap Find, InsertOne, UpdateOne, DeleteOne and other IMongoCollection calls with retry, timeout, circuit-breaker, and more using a single ResilientMongoCollection decorator |
| PollyDapper |
 |
Polly v8 resilience pipelines for Dapper — wrap QueryAsync, ExecuteAsync, and other Dapper calls with retry, timeout, circuit-breaker, and more using a single ResilientDbConnection decorator |
| PollyMediatR |
 |
Polly v8 resilience pipelines for MediatR — add retry, timeout, circuit-breaker, rate-limiting, hedging, and chaos engineering to any MediatR request handler with a single line of DI registration |
| PollySqlClient |
 |
Polly v8 resilience pipelines for Microsoft.Data.SqlClient (SQL Server and Azure SQL) — retry, timeout, and circuit-breaker for SqlConnection queries and commands, plus a built-in SqlServerTransientErrors predicate covering all common SQL Server and Azure SQL transient error numbers |
| PollyAzureQueueStorage |
 |
Polly v8 resilience pipelines for Azure Queue Storage — retry, timeout, and circuit-breaker for Azure.Storage.Queues QueueClient |
| PollyRedis |
 |
Polly v8 resilience for StackExchange.Redis |
| PollyAzureServiceBus |
 |
Polly v8 resilience for Azure Service Bus — retry, circuit breaker, and timeout for sending and receiving messages |
| PollyAzureBlob |
 |
Polly v8 resilience pipelines for Azure Blob Storage — wrap BlobClient and BlobContainerClient operations with retry, timeout, circuit-breaker, and more using ResilientBlobClient and ResilientBlobContainerClient decorators |
| PollyKafka |
 |
Polly v8 resilience for Confluent.Kafka — retry, circuit breaker, and timeout for producers and consumers |
| PollyAzureTableStorage |
 |
Polly v8 resilience pipelines for Azure Table Storage — retry, timeout, and circuit-breaker for Azure.Data.Tables TableClient |
💼 Need .NET consulting?
The author of this package is available for consulting on Polly v8 resilience, Azure cloud architecture, and clean .NET design.
→ solidqualitysolutions.com · LinkedIn
License
MIT © Justin Bannister