-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[Docs] Dependency injection #1564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,224 @@ | ||
| # Dependency Injection | ||
| # Dependency injection | ||
|
|
||
| 🚧 This documentation is being written as part of the Polly v8 release. | ||
| Starting with version 8, Polly provides features that make the integration of Polly with the standard [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) Dependency Injection (DI) container more streamlined. This is a thin layer atop the [resilience pipeline registry](resilience-pipeline-registry.md) which manages resilience pipelines. | ||
|
|
||
| ## Usage | ||
|
|
||
| To use the DI functionality, add the `Polly.Extensions` package to your project: | ||
|
|
||
| ```sh | ||
| dotnet add package Polly.Extensions | ||
| ``` | ||
|
|
||
| Afterwards, you can use the `AddResiliencePipeline(...)` extension method to set up your pipeline: | ||
|
|
||
| <!-- snippet: add-resilience-pipeline --> | ||
| ```cs | ||
| var services = new ServiceCollection(); | ||
|
|
||
| // Define a resilience pipeline | ||
| services.AddResiliencePipeline("my-key", builder => | ||
| { | ||
| // Add strategies to your pipeline here, timeout for example | ||
| builder.AddTimeout(TimeSpan.FromSeconds(10)); | ||
| }); | ||
|
|
||
| // You can also access IServiceProvider by using the alternate overload | ||
| services.AddResiliencePipeline("my-key", (builder, context) => | ||
| { | ||
| // Resolve any service from DI | ||
| var loggerFactory = context.ServiceProvider.GetRequiredService<ILoggerFactory>(); | ||
|
|
||
| // Add strategies to your pipeline here | ||
| builder.AddTimeout(TimeSpan.FromSeconds(10)); | ||
| }); | ||
|
|
||
| // Resolve the resilience pipeline | ||
| ServiceProvider serviceProvider = services.BuildServiceProvider(); | ||
| ResiliencePipelineProvider<string> pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>(); | ||
| ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-key"); | ||
|
|
||
| // Use it | ||
| await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| The `AddResiliencePipeline` extension method also registers the following services into the DI: | ||
|
|
||
| - `ResiliencePipelineRegistry<string>`: Allows adding and retrieving resilience pipelines. | ||
| - `ResiliencePipelineProvider<string>`: Allows retrieving resilience pipelines. | ||
| - `IOptions<ResiliencePipelineRegistryOptions<string>>`: Options for `ResiliencePipelineRegistry<string>`. | ||
|
|
||
| > [!NOTE] The generic `string`` is inferred since the pipeline was defined using the "my-key" value. | ||
|
|
||
| If you only need the registry without defining a pipeline, use the `AddResiliencePipelineRegistry(...)` method. | ||
|
|
||
| ### Generic resilience pipelines | ||
|
|
||
| You can also define generic resilience pipelines (`ResiliencePipeline<T>`), as demonstrated below: | ||
|
|
||
| <!-- snippet: add-resilience-pipeline-generic --> | ||
| ```cs | ||
| var services = new ServiceCollection(); | ||
|
|
||
| // Define a generic resilience pipeline | ||
| // First parameter is the type of key, second one is the type of the results the generic pipeline works with | ||
| services.AddResiliencePipeline<string, HttpResponseMessage>("my-pipeline", builder => | ||
| { | ||
| builder.AddRetry(new() | ||
| { | ||
| MaxRetryAttempts = 2, | ||
| ShouldHandle = new PredicateBuilder<HttpResponseMessage>() | ||
| .Handle<HttpRequestException>() | ||
| .Handle<TimeoutRejectedException>() | ||
| .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.InternalServerError) | ||
| }) | ||
| .AddTimeout(TimeSpan.FromSeconds(2)); | ||
| }); | ||
|
|
||
| // Resolve the resilience pipeline | ||
| ServiceProvider serviceProvider = services.BuildServiceProvider(); | ||
| ResiliencePipelineProvider<string> pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>(); | ||
| ResiliencePipeline<HttpResponseMessage> pipeline = pipelineProvider.GetPipeline<HttpResponseMessage>("my-key"); | ||
|
|
||
| // Use it | ||
| await pipeline.ExecuteAsync( | ||
| async cancellation => await client.GetAsync(endpoint, cancellation), | ||
| cancellationToken); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| ## Dynamic reloads | ||
|
|
||
| The dynamic reloads is a feature of pipeline registry that is also surfaced when using the `AddResiliencePipeline(...)` extension method. Use an overload that provides access to `AddResiliencePipelineContext`: | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| <!-- snippet: di-dynamic-reloads --> | ||
| ```cs | ||
| services | ||
| .Configure<RetryStrategyOptions>("my-retry-options", configurationSection) // Configure the options | ||
| .AddResiliencePipeline("my-pipeline", (builder, context) => | ||
| { | ||
| // Enable the reloads whenever the named options change | ||
| context.EnableReloads<RetryStrategyOptions>("my-retry-options"); | ||
|
|
||
| // Utility method to retrieve the named options | ||
| var retryOptions = context.GetOptions<RetryStrategyOptions>("my-retry-options"); | ||
|
|
||
| // Add retries using the resolved options | ||
| builder.AddRetry(retryOptions); | ||
| }); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| - `EnableReloads` activates the dynamic reloading of `my-pipeline`. | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - `RetryStrategyOptions` are fetched using `context.GetOptions(...)` utility method. | ||
| - A retry strategy is added. | ||
|
|
||
| During a reload: | ||
|
|
||
| - The callback re-executes. | ||
| - The previous pipeline is discarded. | ||
|
|
||
| If an error occurs during reloading, the old pipeline remains, and dynamic reloading stops. | ||
|
|
||
| ## Resource disposal | ||
|
|
||
| Like dynamic reloads, the pipeline registry's resource disposal feature lets you register callbacks. These callbacks run when the pipeline is discarded, reloaded, or the registry is disposed at application shutdown. | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| See the example below: | ||
|
|
||
| <!-- snippet: di-resource-disposal --> | ||
| ```cs | ||
| services.AddResiliencePipeline("my-pipeline", (builder, context) => | ||
| { | ||
| // Create disposable resource | ||
| var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 100, QueueLimit = 100 }); | ||
|
|
||
| // Use it | ||
| builder.AddRateLimiter(limiter); | ||
|
|
||
| // Dispose the resource created in the callback when the pipeline is discarded | ||
| context.OnPipelineDisposed(() => limiter.Dispose()); | ||
| }); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| This feature ensures that resources are properly disposed when a pipeline reloads, discarding the old version. | ||
|
|
||
| ## Complex pipeline keys | ||
|
|
||
| The `AddResiliencePipeline(...)` method supports complex pipeline keys. This capability allows you to define the structure of your pipeline and dynamically resolve and cache multiple instances of the pipeline with different keys. | ||
|
|
||
| Start by defining your complex key: | ||
|
|
||
| <!-- snippet: di-registry-complex-key --> | ||
| ```cs | ||
| public record struct MyPipelineKey(string PipelineName, string InstanceName) | ||
| { | ||
| } | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| Next, register your pipeline: | ||
|
|
||
| <!-- snippet: di-registry-add-pipeline --> | ||
| ```cs | ||
| services.AddResiliencePipeline(new MyPipelineKey("my-pipeline", string.Empty), builder => | ||
| { | ||
| // Circuit breaker is a stateful strategy. To isolate the builder across different pipelines, | ||
| // we must use multiple instances. | ||
| builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions()); | ||
| }); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| The "my-pipeline" is now registered. Note that the `InstanceName` is an empty string. While we're registering the builder action for a specific pipeline, the `InstanceName` parameter isn't used during the pipeline's registration. Some further modifications are required for this to function. | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Introduce the `PipelineNameComparer`: | ||
|
|
||
| <!-- snippet: di-complex-key-comparer --> | ||
| ```cs | ||
| public sealed class PipelineNameComparer : IEqualityComparer<MyPipelineKey> | ||
| { | ||
| public bool Equals(MyPipelineKey x, MyPipelineKey y) => x.PipelineName == y.PipelineName; | ||
|
|
||
| public int GetHashCode(MyPipelineKey obj) => (obj.PipelineName, obj.InstanceName).GetHashCode(); | ||
| } | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| Then, configure the registry behavior: | ||
|
|
||
| <!-- snippet: di-registry-configure --> | ||
| ```cs | ||
| services | ||
| .AddResiliencePipelineRegistry<MyPipelineKey>(options => | ||
| { | ||
| options.BuilderComparer = new PipelineNameComparer(); | ||
|
|
||
| options.InstanceNameFormatter = key => key.InstanceName; | ||
|
|
||
| options.BuilderNameFormatter = key => key.PipelineName; | ||
| }); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| Let's summarize our actions: | ||
|
|
||
| - We assigned the `PipelineNameComparer` instance to the `BuilderComparer` property. This action changes the default registry behavior, ensuring that only the `PipelineName` is used to find the associated builder. | ||
| - We used the `InstanceNameFormatter` delegate to represent the `MyPipelineKey` as an instance name for telemetry purposes, keeping the instance name as it is. | ||
| - Likewise, the `BuilderNameFormatter` delegate represents the `MyPipelineKey` as a builder name in telemetry. | ||
|
|
||
| Finally, utilize the `ResiliencePipelineProvider<MyPipelineKey>` to dynamically create and cache multiple instances of the same pipeline: | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| <!-- snippet: di-registry-multiple-instances --> | ||
| ```cs | ||
| ResiliencePipelineProvider<MyPipelineKey> pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<MyPipelineKey>>(); | ||
|
|
||
| // The registry dynamically creates and caches instance-A using the associated builder action | ||
| ResiliencePipeline instanceA = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-A")); | ||
|
|
||
| // The registry creates and caches instance-B | ||
| ResiliencePipeline instanceB = pipelineProvider.GetPipeline(new MyPipelineKey("my-pipeline", "instance-B")); | ||
| ``` | ||
| <!-- endSnippet --> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,6 @@ | ||
| # Polly.Extensions Overview | ||
| # Polly.extensions overview | ||
martincostello marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| `Polly.Extensions` provides a set of features that streamline the integration of Polly with the standard `IServiceCollection` Dependency Injection (DI) container. It further enhances telemetry by exposing a `ConfigureTelemetry` extension method that enables [logging](https://learn.microsoft.com/dotnet/core/extensions/logging?tabs=command-line) and [metering](https://learn.microsoft.com/dotnet/core/diagnostics/metrics) for all strategies created via DI extension points. | ||
| This project provides the following features: | ||
|
|
||
| Below is an example illustrating the usage of `AddResiliencePipeline` extension method: | ||
|
|
||
| <!-- snippet: add-resilience-pipeline --> | ||
| ```cs | ||
| var services = new ServiceCollection(); | ||
|
|
||
| // Define a resilience pipeline | ||
| services.AddResiliencePipeline( | ||
| "my-key", | ||
| builder => builder.AddTimeout(TimeSpan.FromSeconds(10))); | ||
|
|
||
| // Define a resilience pipeline with custom options | ||
| services | ||
| .Configure<MyTimeoutOptions>(options => options.Timeout = TimeSpan.FromSeconds(10)) | ||
| .AddResiliencePipeline( | ||
| "my-timeout", | ||
| (builder, context) => | ||
| { | ||
| var myOptions = context.GetOptions<MyTimeoutOptions>(); | ||
|
|
||
| builder.AddTimeout(myOptions.Timeout); | ||
| }); | ||
|
|
||
| // Resolve the resilience pipeline | ||
| var serviceProvider = services.BuildServiceProvider(); | ||
| var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<string>>(); | ||
| var pipeline = pipelineProvider.GetPipeline("my-key"); | ||
|
|
||
| // Use it | ||
| await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation)); | ||
| ``` | ||
| <!-- endSnippet --> | ||
|
|
||
| > [!NOTE] | ||
| > Telemetry is enabled by default when utilizing the `AddResiliencePipeline(...)` extension method. | ||
|
|
||
| ## Telemetry Features | ||
|
|
||
| This project implements the `TelemetryListener` and uses it to bridge the Polly-native events into logs and metrics. | ||
|
|
||
| Explore [telemetry documentation](../../docs/telemetry.md) for more details. | ||
| - Incorporates [dependency injection](../../docs/dependency-injection.md) support and integrates with `IServiceCollection`. | ||
| - Offers [telemetry](../../docs/telemetry.md) support. This is achieved by implementing the `TelemetryListener` and utilizing it to translate the native Polly events into logs and metrics. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.