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

Add new Polly DI extension method for HttpClientFactory #28283

Merged
merged 3 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Provides convenience extension methods to register <see cref="IPolicyRegistry{String}"/> and
/// Provides convenience extension methods to register <see cref="IPolicyRegistry{String}"/> and
/// <see cref="IReadOnlyPolicyRegistry{String}"/> in the service collection.
/// </summary>
public static class PollyServiceCollectionExtensions
Expand All @@ -19,15 +19,15 @@ public static class PollyServiceCollectionExtensions
/// the newly created registry.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <returns>The newly created <see cref="PolicyRegistry"/>.</returns>
/// <returns>The newly created <see cref="IPolicyRegistry{String}"/>.</returns>
public static IPolicyRegistry<string> AddPolicyRegistry(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

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

return registry;
}

/// <summary>
/// Registers an empty <see cref="PolicyRegistry"/> in the service collection with service types
/// <see cref="IPolicyRegistry{String}"/>, and <see cref="IReadOnlyPolicyRegistry{String}"/> and
/// uses the specified delegate to configure it.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configureRegistry">A delegate that is used to configure an <see cref="IPolicyRegistry{String}"/>.</param>
/// <returns>The provided <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddPolicyRegistry(this IServiceCollection services, Action<IServiceProvider, IPolicyRegistry<string>> configureRegistry)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (configureRegistry == null)
{
throw new ArgumentNullException(nameof(configureRegistry));
}

// Create an empty registry, configure it and register it as an instance.
// This is the best way to get a single instance registered using both interfaces.
services.AddSingleton(serviceProvider =>
{
var registry = new PolicyRegistry();

configureRegistry(serviceProvider, registry);

return registry;
});

services.AddSingleton<IPolicyRegistry<string>>(serviceProvider => serviceProvider.GetRequiredService<PolicyRegistry>());
services.AddSingleton<IReadOnlyPolicyRegistry<string>>(serviceProvider => serviceProvider.GetRequiredService<PolicyRegistry>());
JunTaoLuo marked this conversation as resolved.
Show resolved Hide resolved

return services;
}
}
}
1 change: 1 addition & 0 deletions src/HttpClientFactory/Polly/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
~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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public PollyHttpClientBuilderExtensionsTest()
// Allows the exception from our handler to propegate
private IAsyncPolicy<HttpResponseMessage> NoOpPolicy { get; }

// Matches what our client handler does
// Matches what our client handler does
private IAsyncPolicy<HttpResponseMessage> RetryPolicy { get; }

[Fact]
Expand Down Expand Up @@ -255,7 +255,7 @@ public async Task AddPolicyHandlerFromRegistry_Dynamic_AddsPolicyHandler()
public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesStatusCode(HttpStatusCode statusCode)
{
// Arrange
var handler = new SequenceMessageHandler()
using var handler = new SequenceMessageHandler()
{
Responses =
{
Expand Down Expand Up @@ -303,7 +303,7 @@ public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesStatusCod
public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesHttpRequestException()
{
// Arrange
var handler = new SequenceMessageHandler()
using var handler = new SequenceMessageHandler()
{
Responses =
{
Expand Down Expand Up @@ -415,6 +415,59 @@ public async Task AddPolicyHandlerFromRegistry_PolicySelectorWithKey_AddsPolicyH
Assert.True(registry.ContainsKey("host2"));
}

[Fact]
public async Task AddPolicyHandlerFromRegistry_WithConfigureDelegate_AddsPolicyHandler()
{
var options = new PollyPolicyOptions()
{
PolicyName = "retrypolicy"
};

var serviceCollection = new ServiceCollection();

serviceCollection.AddSingleton(options);

serviceCollection.AddPolicyRegistry((serviceProvider, registry) =>
{
string policyName = serviceProvider.GetRequiredService<PollyPolicyOptions>().PolicyName;

registry.Add<IAsyncPolicy<HttpResponseMessage>>(policyName, RetryPolicy);
});

HttpMessageHandlerBuilder builder = null;

// Act1
serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
.AddPolicyHandlerFromRegistry(options.PolicyName)
.ConfigureHttpMessageHandlerBuilder(b =>
{
b.PrimaryHandler = PrimaryHandler;

builder = b;
});

var services = serviceCollection.BuildServiceProvider();
var factory = services.GetRequiredService<IHttpClientFactory>();

// Act2
var client = factory.CreateClient("example.com");

// Assert
Assert.NotNull(client);

Assert.Collection(
builder.AdditionalHandlers,
h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
h => Assert.IsType<PolicyHttpMessageHandler>(h),
h => Assert.IsType<LoggingHttpMessageHandler>(h));

// Act 3
var response = await client.SendAsync(new HttpRequestMessage());

// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}

// Throws an exception or fails on even numbered requests, otherwise succeeds.
private class FaultyMessageHandler : DelegatingHandler
{
Expand Down Expand Up @@ -447,5 +500,10 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
return Task.FromResult(func(request));
}
}

private class PollyPolicyOptions
{
public string PolicyName { get; set; }
}
}
}