Skip to content
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

[Feature] Add support for keyed services #1881

Merged
merged 4 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProv
// 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<ResiliencePipeline>("my-pipeline");

// Execute the pipeline
await pipeline.ExecuteAsync(static async token =>
{
Expand Down
53 changes: 53 additions & 0 deletions docs/advanced/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,56 @@ await pipeline.ExecuteAsync(
```
<!-- endSnippet -->

## 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<T>` using keyed services.

To begin, define your resilience pipeline:

<!-- snippet: di-keyed-services-define -->
```cs
// Define a resilience pipeline
services.AddResiliencePipeline<string, HttpResponseMessage>("my-pipeline", builder =>
{
// Configure the pipeline
});

// Define a generic resilience pipeline
services.AddResiliencePipeline("my-pipeline", builder =>
{
// Configure the pipeline
});
```
<!-- endSnippet -->

Following the definition above, you can resolve the resilience pipelines using keyed services as shown in the example below:

<!-- snippet: di-keyed-services-use -->
```cs
public class MyApi
{
private readonly ResiliencePipeline _pipeline;
private readonly ResiliencePipeline<HttpResponseMessage> _genericPipeline;

public MyApi(
[FromKeyedServices("my-pipeline")]
ResiliencePipeline pipeline,
[FromKeyedServices("my-pipeline")]
ResiliencePipeline<HttpResponseMessage> 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;
}
}
```
<!-- endSnippet -->

> [!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. 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
Expand Down Expand Up @@ -133,6 +183,9 @@ services
```
<!-- endSnippet -->

> [!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, 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
Expand Down
3 changes: 3 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProv
// 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<ResiliencePipeline>("my-pipeline");

// Execute the pipeline
await pipeline.ExecuteAsync(static async token =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ public static IServiceCollection AddResiliencePipeline<TKey, TResult>(
Guard.NotNull(services);
Guard.NotNull(configure);

services.TryAddKeyedTransient(
key,
(serviceProvider, key) =>
{
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<TKey>>();

return pipelineProvider.GetPipeline<TResult>((TKey)key!);
});

return services.AddResiliencePipelines<TKey>((context) =>
{
context.AddResiliencePipeline(key, configure);
Expand Down Expand Up @@ -125,6 +134,14 @@ public static IServiceCollection AddResiliencePipeline<TKey>(
Guard.NotNull(services);
Guard.NotNull(configure);

services.TryAddKeyedTransient(
key,
(serviceProvider, key) =>
{
var pipelineProvider = serviceProvider.GetRequiredService<ResiliencePipelineProvider<TKey>>();
return pipelineProvider.GetPipeline((TKey)key!);
});

return services.AddResiliencePipelines<TKey>((context) =>
{
context.AddResiliencePipeline(key, configure);
Expand Down
43 changes: 43 additions & 0 deletions src/Snippets/Docs/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace Snippets.Docs;

#pragma warning disable IDE0052 // Remove unread private members

internal static class DependencyInjection
{
public static async Task AddResiliencePipeline()
Expand Down Expand Up @@ -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<string, HttpResponseMessage>("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<HttpResponseMessage> _genericPipeline;

public MyApi(
[FromKeyedServices("my-pipeline")]
ResiliencePipeline pipeline,
[FromKeyedServices("my-pipeline")]
ResiliencePipeline<HttpResponseMessage> 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
Expand Down
3 changes: 3 additions & 0 deletions src/Snippets/Docs/Readme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResiliencePipeline>("my-pipeline");

// Execute the pipeline
await pipeline.ExecuteAsync(static async token =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResiliencePipeline>(Key);
provider.GetKeyedService<ResiliencePipeline>(Key).Should().BeSameAs(pipeline);

pipeline.Should().NotBeNull();
}

[Fact]
public void AddResiliencePipeline_GenericKeyedSingleton_Ok()
{
AddResiliencePipeline<string>(Key);

var provider = _services.BuildServiceProvider();

var pipeline = provider.GetKeyedService<ResiliencePipeline<string>>(Key);
provider.GetKeyedService<ResiliencePipeline<string>>(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<ResiliencePipeline>(Key).Should().BeSameAs(pipeline);
}

[Fact]
public void AddResiliencePipeline_GenericKeyedSingletonOverride_Ok()
{
var pipeline = new ResiliencePipelineBuilder<string>().AddTimeout(TimeSpan.FromSeconds(1)).Build();
_services.AddKeyedSingleton(Key, pipeline);
AddResiliencePipeline(Key);

var provider = _services.BuildServiceProvider();

provider.GetKeyedService<ResiliencePipeline<string>>(Key).Should().BeSameAs(pipeline);
}

[InlineData(true)]
[InlineData(false)]
[Theory]
Expand Down