Skip to content

Commit 52336bf

Browse files
Add new Polly DI extension method for HttpClientFactory (#28283)
* Fix IDE warnings Fix IDE warnings about non-disposed handlers. * Update return type documentation Update the comment's type to match the method's. * Add new AddPolicyRegistry overload Add a new overload for AddPolicyRegistry() that allows the caller to configure the policy registry using other services that are registered with the service provider, such as config.
1 parent d5a9658 commit 52336bf

File tree

3 files changed

+102
-6
lines changed

3 files changed

+102
-6
lines changed

src/HttpClientFactory/Polly/src/DependencyInjection/PollyServiceCollectionExtensions.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Microsoft.Extensions.DependencyInjection
99
{
1010
/// <summary>
11-
/// Provides convenience extension methods to register <see cref="IPolicyRegistry{String}"/> and
11+
/// Provides convenience extension methods to register <see cref="IPolicyRegistry{String}"/> and
1212
/// <see cref="IReadOnlyPolicyRegistry{String}"/> in the service collection.
1313
/// </summary>
1414
public static class PollyServiceCollectionExtensions
@@ -19,15 +19,15 @@ public static class PollyServiceCollectionExtensions
1919
/// the newly created registry.
2020
/// </summary>
2121
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
22-
/// <returns>The newly created <see cref="PolicyRegistry"/>.</returns>
22+
/// <returns>The newly created <see cref="IPolicyRegistry{String}"/>.</returns>
2323
public static IPolicyRegistry<string> AddPolicyRegistry(this IServiceCollection services)
2424
{
2525
if (services == null)
2626
{
2727
throw new ArgumentNullException(nameof(services));
2828
}
2929

30-
// Create an empty registry, register and return it as an instance. This is the best way to get a
30+
// Create an empty registry, register and return it as an instance. This is the best way to get a
3131
// single instance registered using both interfaces.
3232
var registry = new PolicyRegistry();
3333
services.AddSingleton<IPolicyRegistry<string>>(registry);
@@ -61,5 +61,42 @@ public static IPolicyRegistry<string> AddPolicyRegistry(this IServiceCollection
6161

6262
return registry;
6363
}
64+
65+
/// <summary>
66+
/// Registers an empty <see cref="PolicyRegistry"/> in the service collection with service types
67+
/// <see cref="IPolicyRegistry{String}"/>, and <see cref="IReadOnlyPolicyRegistry{String}"/> and
68+
/// uses the specified delegate to configure it.
69+
/// </summary>
70+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
71+
/// <param name="configureRegistry">A delegate that is used to configure an <see cref="IPolicyRegistry{String}"/>.</param>
72+
/// <returns>The provided <see cref="IServiceCollection"/>.</returns>
73+
public static IServiceCollection AddPolicyRegistry(this IServiceCollection services, Action<IServiceProvider, IPolicyRegistry<string>> configureRegistry)
74+
{
75+
if (services == null)
76+
{
77+
throw new ArgumentNullException(nameof(services));
78+
}
79+
80+
if (configureRegistry == null)
81+
{
82+
throw new ArgumentNullException(nameof(configureRegistry));
83+
}
84+
85+
// Create an empty registry, configure it and register it as an instance.
86+
// This is the best way to get a single instance registered using both interfaces.
87+
services.AddSingleton(serviceProvider =>
88+
{
89+
var registry = new PolicyRegistry();
90+
91+
configureRegistry(serviceProvider, registry);
92+
93+
return registry;
94+
});
95+
96+
services.AddSingleton<IPolicyRegistry<string>>(serviceProvider => serviceProvider.GetRequiredService<PolicyRegistry>());
97+
services.AddSingleton<IReadOnlyPolicyRegistry<string>>(serviceProvider => serviceProvider.GetRequiredService<PolicyRegistry>());
98+
99+
return services;
100+
}
64101
}
65102
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
#nullable enable
2+
~static Microsoft.Extensions.DependencyInjection.PollyServiceCollectionExtensions.AddPolicyRegistry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<System.IServiceProvider, Polly.Registry.IPolicyRegistry<string>> configureRegistry) -> Microsoft.Extensions.DependencyInjection.IServiceCollection

src/HttpClientFactory/Polly/test/DependencyInjection/PollyHttpClientBuilderExtensionsTest.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public PollyHttpClientBuilderExtensionsTest()
3131
// Allows the exception from our handler to propegate
3232
private IAsyncPolicy<HttpResponseMessage> NoOpPolicy { get; }
3333

34-
// Matches what our client handler does
34+
// Matches what our client handler does
3535
private IAsyncPolicy<HttpResponseMessage> RetryPolicy { get; }
3636

3737
[Fact]
@@ -255,7 +255,7 @@ public async Task AddPolicyHandlerFromRegistry_Dynamic_AddsPolicyHandler()
255255
public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesStatusCode(HttpStatusCode statusCode)
256256
{
257257
// Arrange
258-
var handler = new SequenceMessageHandler()
258+
using var handler = new SequenceMessageHandler()
259259
{
260260
Responses =
261261
{
@@ -303,7 +303,7 @@ public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesStatusCod
303303
public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesHttpRequestException()
304304
{
305305
// Arrange
306-
var handler = new SequenceMessageHandler()
306+
using var handler = new SequenceMessageHandler()
307307
{
308308
Responses =
309309
{
@@ -415,6 +415,59 @@ public async Task AddPolicyHandlerFromRegistry_PolicySelectorWithKey_AddsPolicyH
415415
Assert.True(registry.ContainsKey("host2"));
416416
}
417417

418+
[Fact]
419+
public async Task AddPolicyHandlerFromRegistry_WithConfigureDelegate_AddsPolicyHandler()
420+
{
421+
var options = new PollyPolicyOptions()
422+
{
423+
PolicyName = "retrypolicy"
424+
};
425+
426+
var serviceCollection = new ServiceCollection();
427+
428+
serviceCollection.AddSingleton(options);
429+
430+
serviceCollection.AddPolicyRegistry((serviceProvider, registry) =>
431+
{
432+
string policyName = serviceProvider.GetRequiredService<PollyPolicyOptions>().PolicyName;
433+
434+
registry.Add<IAsyncPolicy<HttpResponseMessage>>(policyName, RetryPolicy);
435+
});
436+
437+
HttpMessageHandlerBuilder builder = null;
438+
439+
// Act1
440+
serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
441+
.AddPolicyHandlerFromRegistry(options.PolicyName)
442+
.ConfigureHttpMessageHandlerBuilder(b =>
443+
{
444+
b.PrimaryHandler = PrimaryHandler;
445+
446+
builder = b;
447+
});
448+
449+
var services = serviceCollection.BuildServiceProvider();
450+
var factory = services.GetRequiredService<IHttpClientFactory>();
451+
452+
// Act2
453+
var client = factory.CreateClient("example.com");
454+
455+
// Assert
456+
Assert.NotNull(client);
457+
458+
Assert.Collection(
459+
builder.AdditionalHandlers,
460+
h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
461+
h => Assert.IsType<PolicyHttpMessageHandler>(h),
462+
h => Assert.IsType<LoggingHttpMessageHandler>(h));
463+
464+
// Act 3
465+
var response = await client.SendAsync(new HttpRequestMessage());
466+
467+
// Assert
468+
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
469+
}
470+
418471
// Throws an exception or fails on even numbered requests, otherwise succeeds.
419472
private class FaultyMessageHandler : DelegatingHandler
420473
{
@@ -447,5 +500,10 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
447500
return Task.FromResult(func(request));
448501
}
449502
}
503+
504+
private class PollyPolicyOptions
505+
{
506+
public string PolicyName { get; set; }
507+
}
450508
}
451509
}

0 commit comments

Comments
 (0)