EasyCore.Polly
8.3.2
See the version list below for details.
dotnet add package EasyCore.Polly --version 8.3.2
NuGet\Install-Package EasyCore.Polly -Version 8.3.2
<PackageReference Include="EasyCore.Polly" Version="8.3.2" />
<PackageVersion Include="EasyCore.Polly" Version="8.3.2" />
<PackageReference Include="EasyCore.Polly" />
paket add EasyCore.Polly --version 8.3.2
#r "nuget: EasyCore.Polly, 8.3.2"
#:package EasyCore.Polly@8.3.2
#addin nuget:?package=EasyCore.Polly&version=8.3.2
#tool nuget:?package=EasyCore.Polly&version=8.3.2
π‘οΈ EasyCore.Polly
EasyCore.Polly is a production-ready Polly resilience wrapper for .NET 8. Apply retry, timeout, circuit breaker, bulkhead, and fallback with
[PollyConfig]on interface / class / method / MVC API. Standalone NuGet (does not depend on EasyCore.Invocation); stacks with Redis / Invocation via DIIAsyncInterceptor.
<p align="center"> <img src="https://raw.githubusercontent.com/RockyWang0521/EasyCore.Polly/master/png/EasyCoreLogo.png" alt="EasyCore Logo" width="120" /> </p>
π Language
- Chinese: README.md
- English (this document)
π Table of Contents
Part I β Overview & Architecture
Part II β Getting Started
Part III β Policies & Production
1. π― Positioning
EasyCore.Polly solves βdeclare resilience with attributes instead of hand-written Polly wrapsβ:
| Pain point | EasyCore.Polly approach |
|---|---|
| Hand-written Polly wraps everywhere | [PollyConfig] on interface / class / method / API |
| Manual proxy registration | AddEasyCorePolly + discovery |
| Fragile filesystem scanning | Prefer AddAssemblyFrom<T>() |
Broken Task<T> fallback |
Generic policies return degradation values |
| Library forces console logging | Host owns logging configuration |
| Coupling to other AOP packages | Independent; stack via DI IAsyncInterceptor |
1.1 Design Principles
| Principle | Meaning |
|---|---|
| Low friction | One extension method + one attribute to get running |
| Multi-level placement | Interface type / interface method / class / method / ControllerΒ·Action |
| Interface proxy | Castle DynamicProxy; APIs use IFilterFactory |
| Explicit assemblies | Use AddAssemblyFrom<T>() in production |
| Composable policies | Retry / Timeout / CB / Bulkhead / Fallback in one attribute |
| Package independence | No reference to EasyCore.Invocation / Redis; stack via DI |
2. ποΈ Architecture & Call Chain
2.1 Component diagram
2.2 Call sequence
[IOrderClient] βββΊ Castle Interface Proxy
β
βΌ
PollyStandardInterceptor
β
βΌ
PolicyCache βββΊ PolicyFactory
β
βΌ
Fallback β Retry β Timeout
β CircuitBreaker β Bulkhead
β
βΌ
OrderClient.Method()
3. π Repository Layout
EasyCore.Polly/
βββ src/EasyCore.Polly/
β βββ Abstractions/ # Options, Attribute
β βββ Discovery/ # Assembly discovery
β βββ Hosting/ # DI extension entry
β βββ Interception/ # Castle interceptor
β βββ Policies/ # Policy factory and cache
βββ demo/
β βββ PollyTest/ # Shared sample services
β βββ WebApp.EasyCorePolly/ # ASP.NET Core + Swagger
β βββ WinFormsApp.EasyCorePolly/ # WinForms
βββ tests/EasyCore.Polly.Tests/
βββ docs/svg/ # README diagrams
βββ png/EasyCoreLogo.png
Folders are for organization only; the public namespace stays
EasyCore.Polly.
4. π¦ Installation
dotnet add package EasyCore.Polly
Requires .NET 8.
5. β‘ Quick Start
5.1 Define an interface and implementation
using EasyCore.Polly;
public interface IOrderClient
{
[PollyConfig(
MaxRetry = 3,
RetryIntervalSeconds = 1,
TimeOutSeconds = 5,
IsEnableCircuitBreaker = true,
ExceptionsAllowedBeforeBreaking = 5,
SecondsOfBreak = 30,
ServiceDegradation = nameof(GetOrderFallbackAsync))]
Task<string> GetOrderAsync(string id);
Task<string> GetOrderFallbackAsync();
}
public sealed class OrderClient : IOrderClient
{
public Task<string> GetOrderAsync(string id)
=> throw new HttpRequestException("upstream down");
public Task<string> GetOrderFallbackAsync()
=> Task.FromResult("cached-or-default");
}
Services need an interface proxy. You can also put
[PollyConfig]on Controller / Action (no global filter).
5.2 Register services
Either style works:
// Convenient: auto-scan candidate assemblies (demo / local)
builder.Services.AddEasyCorePolly();
// Production: scan an explicit assembly
builder.Services.AddEasyCorePolly(options =>
{
options.AddAssemblyFrom<OrderClient>();
});
Resolve IOrderClient from DI β you get a Castle proxy that applies policies.
6. βοΈ Attributes & Options
6.0 Placement
| Placement | Hits | Path |
|---|---|---|
| Interface type | All methods on that interface | Castle |
| Interface method | That method only | Castle |
| Implementation class | Outward methods of the class | Castle |
| Implementation method | That method only | Castle |
| Controller / Action | That type or action | MVC IFilterFactory |
| EasyCoreAppService contract / method | Dynamic API action | Convention merges interface attrs β IFilterFactory |
Resolution: impl method β interface method β class β interface type. Default Order is 50 (typically outside cache).
MVC / Dynamic API and retry:
ActionExecutionDelegatemay run only once, so the filter path forcesMaxRetry = 0(timeout / circuit breaker / bulkhead / fallback still apply). Use Castle proxies for retries (plain services, or AspNetCore.Mvc remoteDispatchProxywrapped with interceptors).
6.1 [PollyConfig] property reference
| Property | Meaning | Default | Icon |
|---|---|---|---|
RateLimit |
Bulkhead max parallelization (0 = off). Not a per-second rate limiter. |
0 |
π§± |
RateQueuing |
Bulkhead max queued actions | 0 |
π₯ |
MaxRetry |
Retry count (0 = off) |
0 |
π |
RetryIntervalSeconds |
Delay between retries | 1 |
β±οΈ |
IsEnableCircuitBreaker |
Enable circuit breaker | false |
β‘ |
ExceptionsAllowedBeforeBreaking |
Failures before open | 3 |
π₯ |
SecondsOfBreak |
Open duration (seconds) | 5 |
βΈοΈ |
TimeOutSeconds |
Timeout (0 = off) |
0 |
β |
ServiceDegradation |
Parameterless fallback method name on the same type | null |
π©Ή |
6.2 EasyCorePollyOptions
| API | Meaning |
|---|---|
AddAssemblyFrom<T>() |
Scan the assembly that contains T (production recommended) |
AddAssembly(Assembly) |
Scan an explicit assembly |
Assemblies |
Configured assembly list (empty β candidate scan) |
7. π§© Policy Wrap Order
7.1 Visual wrap
7.2 Execution order
Outermost β innermost:
Fallback β Retry β Timeout β CircuitBreaker β Bulkhead β method
This matches Polly WrapAsync composition used by EasyCore.Polly.
| Policy | Role | Property |
|---|---|---|
| π©Ή Fallback | Degrade on final failure | ServiceDegradation |
| π Retry | Transient fault retries | MaxRetry / RetryIntervalSeconds |
| β Timeout | Prevent long hangs | TimeOutSeconds |
| β‘ CircuitBreaker | Stop cascading failures | IsEnableCircuitBreaker, etc. |
| π§± Bulkhead | Cap concurrency / isolate faults | RateLimit / RateQueuing |
8. π§ͺ Demo Projects
| Project | Description | Scenario |
|---|---|---|
demo/WebApp.EasyCorePolly |
ASP.NET Core + Swagger | Polly samples + cross-stack /api/combo |
demo/WinFormsApp.EasyCorePolly |
WinForms | Desktop sample |
demo/PollyTest |
Shared [PollyConfig] services |
Referenced by demos above |
Cross-stack (host references Invocation + Redis; packages stay independent):
GET /api/demo # AβF placement guide
GET /api/users/unstable # A interface-type [PollyConfig]
GET /api/orders # B class-type
GET /api/reports/generate
GET /api/notify/send
GET /api/catalog/publish
GET /api/apiplacement/ping
GET /api/combo # H three-package stack
dotnet test tests/EasyCore.Polly.Tests
dotnet run --project demo/WebApp.EasyCorePolly
9. β Production Checklist
- Call
AddAssemblyFrom<T>()(do not rely on base-directory DLL scanning) - Expose services through interfaces (
I{ClassName}) - Configure logging in the host (console/file/OTLP) β the library does not add sinks
- Tune circuit-breaker and timeout values for your SLOs
- Prefer returning values from degradation methods for
Task<T>APIs - Invalid
[PollyConfig]values throw at first execution
10. β FAQ
Q: Why must I use an interface?
A: For services, policies apply via Castle interface proxies. You can also put [PollyConfig] on Controller / Action (IFilterFactory).
Q: Why does MaxRetry not apply on Controller / EasyCoreAppService?
A: MVC filter next can execute only once; retrying would break the pipeline. PollyConfigActionFilter therefore clears MaxRetry on the filter path. Hitting a dynamic API directly (e.g. /api/Xxx/GetUnstableAsync) will not retry; MaxRetry applies on Castle service proxies and on Consumer remote proxies (Castle over HTTP).
Q: Is RateLimit a per-second throttle?
A: No. It maps to Polly Bulkhead (max concurrent executions + optional queue). See the wrap diagram above.
Q: What can a fallback method return?
A: Task, Task<T>, or a synchronous T for generic methods. It must be parameterless and on the same type.
Q: Can I use this with EasyCore.Redis / EasyCore.Invocation?
A: Yes. They are independent NuGet packages. Each registers IAsyncInterceptor via TryAddEnumerable; proxies stack with GetServices. Do not use single-slot TryAdd<IAsyncInterceptor> or only the first package wins.
Q: How does this relate to Ocelot / API gateways?
A: EasyCore.Polly is in-process resilience. Gateways handle edge traffic; use both when appropriate.
Q: Polly v8?
A: This package targets Polly 7.2.x so the attribute model stays stable. Polly v8 pipelines are a separate migration.
11. π License
MIT
π€ Contributing
- Fork and create a feature branch
- Add tests under
tests/EasyCore.Polly.Tests - Run
dotnet testanddotnet build EasyCore.Polly.sln - Open a pull request
Issues / PRs welcome π
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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 was computed. 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 was computed. 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. |
-
net8.0
- AspectInjector (>= 2.8.2)
- EasyCore.Ambient (>= 8.3.2)
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
- Polly (>= 7.2.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.