CSharpEssentials.Resilience
6.5.2
dotnet add package CSharpEssentials.Resilience --version 6.5.2
NuGet\Install-Package CSharpEssentials.Resilience -Version 6.5.2
<PackageReference Include="CSharpEssentials.Resilience" Version="6.5.2" />
<PackageVersion Include="CSharpEssentials.Resilience" Version="6.5.2" />
<PackageReference Include="CSharpEssentials.Resilience" />
paket add CSharpEssentials.Resilience --version 6.5.2
#r "nuget: CSharpEssentials.Resilience, 6.5.2"
#:package CSharpEssentials.Resilience@6.5.2
#addin nuget:?package=CSharpEssentials.Resilience&version=6.5.2
#tool nuget:?package=CSharpEssentials.Resilience&version=6.5.2
CSharpEssentials.Resilience
HTTP-agnostic resilience patterns (Retry, Timeout, Circuit Breaker, Fallback) with Result<T> integration. Composable ResiliencePolicy builder backed by Polly v8 (Polly.Core) for transient fault handling without exceptions.
Installation
dotnet add package CSharpEssentials.Resilience
Namespaces
using CSharpEssentials.Resilience; // ResiliencePolicy, ResiliencePolicy<T>, options records, Func and Result extensions
using CSharpEssentials.ResultPattern; // Result, Result<T>
using CSharpEssentials.Errors; // Error, ErrorType
The samples below assume these usings.
Quick Start
using 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 (fallback needs the typed ResiliencePolicy<T>)
Result<Product> product = await ResiliencePolicy<Product>
.Create()
.WithCircuitBreaker(minimumThroughput: 10, failureRatio: 0.5)
.WithFallback(ct => _cache.GetAsync<Product>(id, ct))
.ExecuteAsync(_ => _productService.GetProduct(id));
Policy Builder
Both ResiliencePolicy and ResiliencePolicy<T> are immutable structs: every With... call returns a new policy.
| Method | Parameters (defaults) | Notes |
|---|---|---|
WithRetry |
maxAttempts (3), delay (1 s), exponentialBackoff (true) |
maxAttempts is the number of retries after the first attempt, so the operation runs at most maxAttempts + 1 times. With exponentialBackoff the delay doubles after each retry |
WithTimeout |
timeout |
Polly accepts 1 second to 1 day; any other value throws ValidationException when the policy is built |
WithCircuitBreaker |
minimumThroughput (10), samplingDuration (1 min), breakDuration (30 s), failureRatio (0.5) |
Counts the same failures as the retry strategy |
WithFallback |
Func<CancellationToken, Task<T>> or Func<CancellationToken, Task<Result<T>>> |
ResiliencePolicy<T> only; runs after the other strategies are exhausted |
Create(ResiliencePolicyOptions) |
Retry, Timeout, CircuitBreaker records |
ResiliencePolicy only. WithRetry(RetryOptions), WithTimeout(TimeoutOptions) and WithCircuitBreaker(CircuitBreakerOptions) take the records on both policies |
Create(Action<ResiliencePipelineBuilder>), FromPipeline(pipeline), ToPipeline() |
Interoperate with raw Polly pipelines (ResiliencePipelineBuilder<Result<T>> for ResiliencePolicy<T>) |
Strategies nest in the order you add them: the first one is the outermost. WithRetry(3).WithTimeout(TimeSpan.FromSeconds(1)) applies the timeout to each attempt, while WithTimeout(TimeSpan.FromSeconds(1)).WithRetry(3) limits all attempts together. WithFallback always wraps the strategies of the policy it is called on.
Retrying Failed Results
Both ResiliencePolicy and ResiliencePolicy<T> retry exceptions and failed Result / Result<T> values. The circuit breaker counts the same failures. The non-generic ResiliencePolicy checks any return value that implements IResultBase; other return values count only when the delegate throws.
Errors of type Unauthorized, Forbidden, NotFound and Validation are not retried, because running the operation again gives the same answer. Every other failed result (Failure, Unexpected, Conflict, …) is treated as transient. When the retries run out, the last failed result is returned.
Result<Order> order = await ResiliencePolicy
.Create()
.WithRetry(maxAttempts: 3)
.ExecuteAsync(ct => _orderService.TryGetOrder(id, ct)); // returns Result<Order>
Delegate Extensions
using CSharpEssentials.Resilience;
// Direct execution — wraps any Func<Task<T>> in a Result
Func<Task<User>> getUser = () => _db.GetUser(id);
Result<User> user = await getUser.ExecuteAsync();
// With CancellationToken
Func<CancellationToken, Task<User>> getUserWithToken = ct => _db.GetUser(id, ct);
Result<User> userWithToken = await getUserWithToken.ExecuteAsync(cancellationToken);
Result Retry
// Retry a failed Result directly
Func<CancellationToken, Task<Result<User>>> operation = ct => _db.TryGetUser(id, ct); // returns Result<User>
Result<User> result = await operation.RetryIfFailed(maxAttempts: 3, delay: TimeSpan.FromSeconds(1));
By default every failure is retried except Unauthorized, Forbidden, NotFound and Validation errors. Pass a shouldRetry predicate to decide yourself:
// Retry only errors you classify as transient
Result<User> result = await operation.RetryIfFailed(
shouldRetry: error => error.Code == "Db.Timeout",
maxAttempts: 3,
delay: TimeSpan.FromSeconds(1));
The predicate replaces the default classification. An exception thrown by the operation is converted to the error that would be returned for it (ErrorType.Unexpected) and passed to the predicate too, so an exception the predicate rejects is not retried. Caller cancellation works as without a predicate: it is never retried and throws OperationCanceledException.
Configuration Options
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));
Error Handling
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 Error.Exception(ex, ErrorType.Unexpected), with the exception type name as code.
Cancellation of the caller's token is not converted to an error. When the CancellationToken passed to ExecuteAsync or RetryIfFailed is cancelled and the operation throws OperationCanceledException, the exception is rethrown and WithFallback does not run the fallback. If the caller cancels after an attempt returned a retryable failed Result, ExecuteAsync, RetryIfFailed and WithFallback throw OperationCanceledException instead of returning that result. This is the same whether the cancel lands during an attempt or during the backoff delay, and it also applies right after the final attempt: cancellation wins over returning the last failed result. It also applies to policies without a retry strategy, such as a circuit-breaker-only or timeout-only policy. This rule covers returned failures only: if the operation throws an exception other than OperationCanceledException while the caller's token is cancelled, the exception is returned as an Unexpected error. An OperationCanceledException from another token, such as an HttpClient timeout, is treated as a normal failure: it is retried and it triggers the fallback.
A default(ResiliencePolicy) or default(ResiliencePolicy<T>) behaves like Create().
Pipeline Composition
// Stack multiple policies
Result<Data> result = await ResiliencePolicy
.Create()
.WithRetry(3)
.WithTimeout(TimeSpan.FromSeconds(5))
.WithCircuitBreaker(minimumThroughput: 10)
.ExecuteAsync(_ => _api.GetData());
Polly Integration
Under the hood, uses the Polly v8 ResiliencePipeline from the Polly.Core package. The package does not reference the full Polly package; add it yourself if you use the Polly v7 API (Policy, AsyncRetryPolicy, …).
// Advanced: Direct Polly configuration
using Polly;
using Polly.Retry;
Result<Data> result = await ResiliencePolicy
.Create(builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential
});
})
.ExecuteAsync(_ => _api.GetData());
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- CSharpEssentials.Errors (>= 6.5.2)
- CSharpEssentials.Results (>= 6.5.2)
- Polly.Core (>= 8.0.0 && < 9.0.0)
- System.Text.Json (>= 9.0.4)
-
net10.0
- CSharpEssentials.Errors (>= 6.5.2)
- CSharpEssentials.Results (>= 6.5.2)
- Polly.Core (>= 8.0.0 && < 9.0.0)
-
net11.0
- CSharpEssentials.Errors (>= 6.5.2)
- CSharpEssentials.Results (>= 6.5.2)
- Polly.Core (>= 8.0.0 && < 9.0.0)
-
net9.0
- CSharpEssentials.Errors (>= 6.5.2)
- CSharpEssentials.Results (>= 6.5.2)
- Polly.Core (>= 8.0.0 && < 9.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on CSharpEssentials.Resilience:
| Package | Downloads |
|---|---|
|
CSharpEssentials.GcpSecretManager
A powerful .NET configuration provider for Google Cloud Secret Manager that enables seamless secret management in your applications. Features include region-specific secret handling, flexible filtering options, batch processing, automatic secret rotation, built-in retry policies, and JSON value flattening support. Perfect for cloud-native applications requiring secure and scalable secret management. |
|
|
CSharpEssentials.Http
HttpClient extensions that bridge HTTP calls to the Result pattern. Provides GetFromJsonAsResultAsync, PostAsJsonAsResultAsync, status code mapping, and Polly resilience integration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.5.2 | 117 | 10/10/2026 |
| 6.5.1 | 122 | 10/10/2026 |
| 6.5.0 | 123 | 10/9/2026 |
| 6.4.0 | 120 | 10/9/2026 |
| 6.3.0 | 143 | 10/8/2026 |
| 6.2.0 | 167 | 10/8/2026 |
| 6.1.0 | 170 | 10/7/2026 |
| 6.0.0 | 183 | 10/7/2026 |
| 5.2.1 | 173 | 10/7/2026 |
| 5.2.0 | 175 | 10/7/2026 |
| 5.1.0 | 162 | 10/7/2026 |
| 5.0.0 | 173 | 10/6/2026 |
| 4.1.0 | 166 | 10/6/2026 |
| 4.0.0 | 182 | 10/5/2026 |
| 3.2.3 | 311 | 5/31/2026 |
| 3.2.2 | 199 | 5/31/2026 |
| 3.2.1 | 192 | 5/31/2026 |
| 3.2.0 | 182 | 5/30/2026 |