Skip to content
Closed
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,8 +8,8 @@
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Provides convenience extension methods to register <see cref="IPolicyRegistry{String}"/> and
/// <see cref="IReadOnlyPolicyRegistry{String}"/> in the service collection.
/// 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,7 +19,7 @@ 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)
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>());

return services;
}
}
}
Original file line number Diff line number Diff line change
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; }
}
}
}