HTTP-agnostic resilience patterns (Retry, Timeout, Circuit Breaker, Fallback) with Result<T> integration. Composable ResiliencePolicy builder backed by Polly v8 for transient fault handling without exceptions.
dotnet add package CSharpEssentials.Resilienceusing CSharpEssentials.Resilience;
// Simple retry
Result<User> user = await ResiliencePolicy
.Create()
.WithRetry(maxAttempts: 3, delay: TimeSpan.FromSeconds(1))
.ExecuteAsync(_ => _db.GetUser(id));
// Retry + Timeout
Result<Order> order = await ResiliencePolicy
.Create()
.WithRetry(3)
.WithTimeout(TimeSpan.FromSeconds(5))
.ExecuteAsync(_ => _orderService.GetOrder(id));
// Circuit Breaker + Fallback
Result<Product> product = await ResiliencePolicy
.Create()
.WithCircuitBreaker(minimumThroughput: 10, failureRatio: 0.5)
.WithFallback(ct => _cache.GetAsync<Product>(id, ct))
.ExecuteAsync(_ => _productService.GetProduct(id));using CSharpEssentials.Resilience;
// Direct execution — wraps any Func<Task<T>> in a Result
Result<User> user = await (() => _db.GetUser(id)).ExecuteAsync();
// With CancellationToken
Result<User> user = await ((ct) => _db.GetUser(id, ct)).ExecuteAsync(cancellationToken);// Retry a failed Result directly
Func<CancellationToken, Task<Result<User>>> operation = ct => _db.GetUser(id);
Result<User> result = await operation.RetryIfFailed(maxAttempts: 3, delay: TimeSpan.FromSeconds(1));using CSharpEssentials.Resilience;
// Use options records
var options = new ResiliencePolicyOptions
{
Retry = new RetryOptions { MaxAttempts = 3, Delay = TimeSpan.FromSeconds(1) },
Timeout = new TimeoutOptions { Timeout = TimeSpan.FromSeconds(5) },
CircuitBreaker = new CircuitBreakerOptions
{
MinimumThroughput = 10,
FailureRatio = 0.5,
BreakDuration = TimeSpan.FromSeconds(30)
}
};
Result<User> user = await ResiliencePolicy
.Create(options)
.ExecuteAsync(_ => _db.GetUser(id));Resilience-specific errors are returned as Error values:
Error.Failure("Resilience.Timeout", "Operation timed out.")Error.Failure("Resilience.CircuitBroken", "Circuit breaker is open.")
When retries exhaust, the last exception is returned as ErrorType.Unexpected.
// Stack multiple policies
Result<Data> result = await ResiliencePolicy
.Create()
.WithRetry(3)
.WithTimeout(TimeSpan.FromSeconds(5))
.WithCircuitBreaker(minimumThroughput: 10)
.ExecuteAsync(_ => _api.GetData());Under the hood, uses Polly v8 ResiliencePipeline:
// Advanced: Direct Polly configuration
Result<Data> result = await ResiliencePolicy
.Create(builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential
});
})
.ExecuteAsync(_ => _api.GetData());