diff --git a/README.md b/README.md index ffd7828f553..76e65d840fc 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,9 @@ var pipelineProvider = serviceProvider.GetRequiredService("my-pipeline"); + // Execute the pipeline await pipeline.ExecuteAsync(static async token => { diff --git a/docs/advanced/dependency-injection.md b/docs/advanced/dependency-injection.md index 302d48b9021..f7396cd39a6 100644 --- a/docs/advanced/dependency-injection.md +++ b/docs/advanced/dependency-injection.md @@ -100,6 +100,56 @@ await pipeline.ExecuteAsync( ``` +## Keyed services + +.NET 8 introduced support for [keyed services](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection#keyed-services). +Starting from version 8.3.0, Polly supports the retrieval of `ResiliencePipeline` or `ResiliencePipeline` using keyed services. + +To begin, define your resilience pipeline: + + +```cs +// Define a resilience pipeline +services.AddResiliencePipeline("my-pipeline", builder => +{ + // Configure the pipeline +}); + +// Define a generic resilience pipeline +services.AddResiliencePipeline("my-pipeline", builder => +{ + // Configure the pipeline +}); +``` + + +Following the definition above, you can resolve the resilience pipelines using keyed services as shown in the example below: + + +```cs +public class MyApi +{ + private readonly ResiliencePipeline _pipeline; + private readonly ResiliencePipeline _genericPipeline; + + public MyApi( + [FromKeyedServices("my-pipeline")] + ResiliencePipeline pipeline, + [FromKeyedServices("my-pipeline")] + ResiliencePipeline genericPipeline) + { + // Although the pipelines are registered with the same key, they are distinct instances. + // One is generic, the other is not. + _pipeline = pipeline; + _genericPipeline = genericPipeline; + } +} +``` + + +> [!NOTE] +> The resilience pipelines are registered in the DI container as transient services. This enables the resolution of multiple instances of `ResiliencePipeline` when [complex pipeline keys](#complex-pipeline-keys) are used. The resilience pipeline is retrieved and registered using `ResiliencePipelineProvider` that is responsible for lifetime management of resilience pipelines. + ## Deferred addition of pipelines If you want to use a key for a resilience pipeline that may not be available @@ -133,6 +183,9 @@ services ``` +> [!NOTE] +> The `AddResiliencePipelines` method does not support keyed services. To enable the resolution of a resilience pipeline using keyed services, you should use the `AddResiliencePipeline` extension method, which adds a single resilience pipeline and registers it into the keyed services. + ## Dynamic reloads Dynamic reloading is a feature of the pipeline registry that is also surfaced when diff --git a/docs/getting-started.md b/docs/getting-started.md index 4348088d87f..9d434d5e7e6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -54,6 +54,9 @@ var pipelineProvider = serviceProvider.GetRequiredService("my-pipeline"); + // Execute the pipeline await pipeline.ExecuteAsync(static async token => { diff --git a/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs index 970cc368127..5b19cae5ef0 100644 --- a/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs +++ b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs @@ -68,6 +68,15 @@ public static IServiceCollection AddResiliencePipeline( Guard.NotNull(services); Guard.NotNull(configure); + services.TryAddKeyedTransient( + key, + (serviceProvider, key) => + { + var pipelineProvider = serviceProvider.GetRequiredService>(); + + return pipelineProvider.GetPipeline((TKey)key!); + }); + return services.AddResiliencePipelines((context) => { context.AddResiliencePipeline(key, configure); @@ -125,6 +134,15 @@ public static IServiceCollection AddResiliencePipeline( Guard.NotNull(services); Guard.NotNull(configure); + services.TryAddKeyedTransient( + key, + (serviceProvider, key) => + { + var pipelineProvider = serviceProvider.GetRequiredService>(); + + return pipelineProvider.GetPipeline((TKey)key!); + }); + return services.AddResiliencePipelines((context) => { context.AddResiliencePipeline(key, configure); diff --git a/src/Snippets/Docs/DependencyInjection.cs b/src/Snippets/Docs/DependencyInjection.cs index 0b0e85b6b0c..0ed345bdc5a 100644 --- a/src/Snippets/Docs/DependencyInjection.cs +++ b/src/Snippets/Docs/DependencyInjection.cs @@ -10,6 +10,8 @@ namespace Snippets.Docs; +#pragma warning disable IDE0052 // Remove unread private members + internal static class DependencyInjection { public static async Task AddResiliencePipeline() @@ -85,6 +87,47 @@ await pipeline.ExecuteAsync( #endregion } + public static async Task KeyedServicesDefine(IServiceCollection services) + { + #region di-keyed-services-define + + // Define a resilience pipeline + services.AddResiliencePipeline("my-pipeline", builder => + { + // Configure the pipeline + }); + + // Define a generic resilience pipeline + services.AddResiliencePipeline("my-pipeline", builder => + { + // Configure the pipeline + }); + + #endregion + } + + #region di-keyed-services-use + + public class MyApi + { + private readonly ResiliencePipeline _pipeline; + private readonly ResiliencePipeline _genericPipeline; + + public MyApi( + [FromKeyedServices("my-pipeline")] + ResiliencePipeline pipeline, + [FromKeyedServices("my-pipeline")] + ResiliencePipeline genericPipeline) + { + // Although the pipelines are registered with the same key, they are distinct instances. + // One is generic, the other is not. + _pipeline = pipeline; + _genericPipeline = genericPipeline; + } + } + + #endregion + public static async Task DeferredAddition(IServiceCollection services) { #region di-deferred-addition diff --git a/src/Snippets/Docs/Readme.cs b/src/Snippets/Docs/Readme.cs index 572d3848c40..8b896202902 100644 --- a/src/Snippets/Docs/Readme.cs +++ b/src/Snippets/Docs/Readme.cs @@ -47,6 +47,9 @@ public static async Task QuickStartDi() // Retrieve your resilience pipeline using the name it was registered with ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline"); + // Alternatively, you can use keyed services to retrieve the resilience pipeline + pipeline = serviceProvider.GetRequiredKeyedService("my-pipeline"); + // Execute the pipeline await pipeline.ExecuteAsync(static async token => { diff --git a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs index 8c8082eba45..3712b0c3041 100644 --- a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs +++ b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs @@ -197,6 +197,56 @@ public void AddResiliencePipeline_Single_Ok() provider.GetPipeline("my-pipeline").Should().BeSameAs(provider.GetPipeline("my-pipeline")); } + [Fact] + public void AddResiliencePipeline_KeyedSingleton_Ok() + { + AddResiliencePipeline(Key); + + var provider = _services.BuildServiceProvider(); + + var pipeline = provider.GetKeyedService(Key); + provider.GetKeyedService(Key).Should().BeSameAs(pipeline); + + pipeline.Should().NotBeNull(); + } + + [Fact] + public void AddResiliencePipeline_GenericKeyedSingleton_Ok() + { + AddResiliencePipeline(Key); + + var provider = _services.BuildServiceProvider(); + + var pipeline = provider.GetKeyedService>(Key); + provider.GetKeyedService>(Key).Should().BeSameAs(pipeline); + + pipeline.Should().NotBeNull(); + } + + [Fact] + public void AddResiliencePipeline_KeyedSingletonOverride_Ok() + { + var pipeline = new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(1)).Build(); + _services.AddKeyedSingleton(Key, pipeline); + AddResiliencePipeline(Key); + + var provider = _services.BuildServiceProvider(); + + provider.GetKeyedService(Key).Should().BeSameAs(pipeline); + } + + [Fact] + public void AddResiliencePipeline_GenericKeyedSingletonOverride_Ok() + { + var pipeline = new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(1)).Build(); + _services.AddKeyedSingleton(Key, pipeline); + AddResiliencePipeline(Key); + + var provider = _services.BuildServiceProvider(); + + provider.GetKeyedService>(Key).Should().BeSameAs(pipeline); + } + [InlineData(true)] [InlineData(false)] [Theory]