Skip to content

Commit 28fba68

Browse files
authored
[Feature] Add support for keyed services (#1881)
Closes #1874
1 parent c3fb2d3 commit 28fba68

File tree

7 files changed

+173
-0
lines changed

7 files changed

+173
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProv
8484
// Retrieve your resilience pipeline using the name it was registered with
8585
ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
8686

87+
// Alternatively, you can use keyed services to retrieve the resilience pipeline
88+
pipeline = serviceProvider.GetRequiredKeyedService<ResiliencePipeline>("my-pipeline");
89+
8790
// Execute the pipeline
8891
await pipeline.ExecuteAsync(static async token =>
8992
{

docs/advanced/dependency-injection.md

+53
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,56 @@ await pipeline.ExecuteAsync(
100100
```
101101
<!-- endSnippet -->
102102

103+
## Keyed services
104+
105+
.NET 8 introduced support for [keyed services](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection#keyed-services).
106+
Starting from version 8.3.0, Polly supports the retrieval of `ResiliencePipeline` or `ResiliencePipeline<T>` using keyed services.
107+
108+
To begin, define your resilience pipeline:
109+
110+
<!-- snippet: di-keyed-services-define -->
111+
```cs
112+
// Define a resilience pipeline
113+
services.AddResiliencePipeline<string, HttpResponseMessage>("my-pipeline", builder =>
114+
{
115+
// Configure the pipeline
116+
});
117+
118+
// Define a generic resilience pipeline
119+
services.AddResiliencePipeline("my-pipeline", builder =>
120+
{
121+
// Configure the pipeline
122+
});
123+
```
124+
<!-- endSnippet -->
125+
126+
Following the definition above, you can resolve the resilience pipelines using keyed services as shown in the example below:
127+
128+
<!-- snippet: di-keyed-services-use -->
129+
```cs
130+
public class MyApi
131+
{
132+
private readonly ResiliencePipeline _pipeline;
133+
private readonly ResiliencePipeline<HttpResponseMessage> _genericPipeline;
134+
135+
public MyApi(
136+
[FromKeyedServices("my-pipeline")]
137+
ResiliencePipeline pipeline,
138+
[FromKeyedServices("my-pipeline")]
139+
ResiliencePipeline<HttpResponseMessage> genericPipeline)
140+
{
141+
// Although the pipelines are registered with the same key, they are distinct instances.
142+
// One is generic, the other is not.
143+
_pipeline = pipeline;
144+
_genericPipeline = genericPipeline;
145+
}
146+
}
147+
```
148+
<!-- endSnippet -->
149+
150+
> [!NOTE]
151+
> 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.
152+
103153
## Deferred addition of pipelines
104154

105155
If you want to use a key for a resilience pipeline that may not be available
@@ -133,6 +183,9 @@ services
133183
```
134184
<!-- endSnippet -->
135185

186+
> [!NOTE]
187+
> 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.
188+
136189
## Dynamic reloads
137190

138191
Dynamic reloading is a feature of the pipeline registry that is also surfaced when

docs/getting-started.md

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProv
5454
// Retrieve your resilience pipeline using the name it was registered with
5555
ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
5656

57+
// Alternatively, you can use keyed services to retrieve the resilience pipeline
58+
pipeline = serviceProvider.GetRequiredKeyedService<ResiliencePipeline>("my-pipeline");
59+
5760
// Execute the pipeline
5861
await pipeline.ExecuteAsync(static async token =>
5962
{

src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs

+18
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ public static IServiceCollection AddResiliencePipeline<TKey, TResult>(
6868
Guard.NotNull(services);
6969
Guard.NotNull(configure);
7070

71+
services.TryAddKeyedTransient(
72+
key,
73+
(serviceProvider, key) =>
74+
{
75+
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<TKey>>();
76+
77+
return pipelineProvider.GetPipeline<TResult>((TKey)key!);
78+
});
79+
7180
return services.AddResiliencePipelines<TKey>((context) =>
7281
{
7382
context.AddResiliencePipeline(key, configure);
@@ -125,6 +134,15 @@ public static IServiceCollection AddResiliencePipeline<TKey>(
125134
Guard.NotNull(services);
126135
Guard.NotNull(configure);
127136

137+
services.TryAddKeyedTransient(
138+
key,
139+
(serviceProvider, key) =>
140+
{
141+
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<TKey>>();
142+
143+
return pipelineProvider.GetPipeline((TKey)key!);
144+
});
145+
128146
return services.AddResiliencePipelines<TKey>((context) =>
129147
{
130148
context.AddResiliencePipeline(key, configure);

src/Snippets/Docs/DependencyInjection.cs

+43
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
namespace Snippets.Docs;
1212

13+
#pragma warning disable IDE0052 // Remove unread private members
14+
1315
internal static class DependencyInjection
1416
{
1517
public static async Task AddResiliencePipeline()
@@ -85,6 +87,47 @@ await pipeline.ExecuteAsync(
8587
#endregion
8688
}
8789

90+
public static async Task KeyedServicesDefine(IServiceCollection services)
91+
{
92+
#region di-keyed-services-define
93+
94+
// Define a resilience pipeline
95+
services.AddResiliencePipeline<string, HttpResponseMessage>("my-pipeline", builder =>
96+
{
97+
// Configure the pipeline
98+
});
99+
100+
// Define a generic resilience pipeline
101+
services.AddResiliencePipeline("my-pipeline", builder =>
102+
{
103+
// Configure the pipeline
104+
});
105+
106+
#endregion
107+
}
108+
109+
#region di-keyed-services-use
110+
111+
public class MyApi
112+
{
113+
private readonly ResiliencePipeline _pipeline;
114+
private readonly ResiliencePipeline<HttpResponseMessage> _genericPipeline;
115+
116+
public MyApi(
117+
[FromKeyedServices("my-pipeline")]
118+
ResiliencePipeline pipeline,
119+
[FromKeyedServices("my-pipeline")]
120+
ResiliencePipeline<HttpResponseMessage> genericPipeline)
121+
{
122+
// Although the pipelines are registered with the same key, they are distinct instances.
123+
// One is generic, the other is not.
124+
_pipeline = pipeline;
125+
_genericPipeline = genericPipeline;
126+
}
127+
}
128+
129+
#endregion
130+
88131
public static async Task DeferredAddition(IServiceCollection services)
89132
{
90133
#region di-deferred-addition

src/Snippets/Docs/Readme.cs

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public static async Task QuickStartDi()
4747
// Retrieve your resilience pipeline using the name it was registered with
4848
ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
4949

50+
// Alternatively, you can use keyed services to retrieve the resilience pipeline
51+
pipeline = serviceProvider.GetRequiredKeyedService<ResiliencePipeline>("my-pipeline");
52+
5053
// Execute the pipeline
5154
await pipeline.ExecuteAsync(static async token =>
5255
{

test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs

+50
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,56 @@ public void AddResiliencePipeline_Single_Ok()
197197
provider.GetPipeline("my-pipeline").Should().BeSameAs(provider.GetPipeline("my-pipeline"));
198198
}
199199

200+
[Fact]
201+
public void AddResiliencePipeline_KeyedSingleton_Ok()
202+
{
203+
AddResiliencePipeline(Key);
204+
205+
var provider = _services.BuildServiceProvider();
206+
207+
var pipeline = provider.GetKeyedService<ResiliencePipeline>(Key);
208+
provider.GetKeyedService<ResiliencePipeline>(Key).Should().BeSameAs(pipeline);
209+
210+
pipeline.Should().NotBeNull();
211+
}
212+
213+
[Fact]
214+
public void AddResiliencePipeline_GenericKeyedSingleton_Ok()
215+
{
216+
AddResiliencePipeline<string>(Key);
217+
218+
var provider = _services.BuildServiceProvider();
219+
220+
var pipeline = provider.GetKeyedService<ResiliencePipeline<string>>(Key);
221+
provider.GetKeyedService<ResiliencePipeline<string>>(Key).Should().BeSameAs(pipeline);
222+
223+
pipeline.Should().NotBeNull();
224+
}
225+
226+
[Fact]
227+
public void AddResiliencePipeline_KeyedSingletonOverride_Ok()
228+
{
229+
var pipeline = new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(1)).Build();
230+
_services.AddKeyedSingleton(Key, pipeline);
231+
AddResiliencePipeline(Key);
232+
233+
var provider = _services.BuildServiceProvider();
234+
235+
provider.GetKeyedService<ResiliencePipeline>(Key).Should().BeSameAs(pipeline);
236+
}
237+
238+
[Fact]
239+
public void AddResiliencePipeline_GenericKeyedSingletonOverride_Ok()
240+
{
241+
var pipeline = new ResiliencePipelineBuilder<string>().AddTimeout(TimeSpan.FromSeconds(1)).Build();
242+
_services.AddKeyedSingleton(Key, pipeline);
243+
AddResiliencePipeline(Key);
244+
245+
var provider = _services.BuildServiceProvider();
246+
247+
provider.GetKeyedService<ResiliencePipeline<string>>(Key).Should().BeSameAs(pipeline);
248+
}
249+
200250
[InlineData(true)]
201251
[InlineData(false)]
202252
[Theory]

0 commit comments

Comments
 (0)