The Microsoft.Extensions method for Polly, to use policies via dependency injection,
serviceCollection.AddPolicyRegistry()
only allows to add already created policies, while still defining the ServiceCollection content, so other DI service instances, such as loggers, are not available.
To resolve the problem and also because I need some additional data on the policies, I created an interface, which provides a policy:
public interface IPolicySource
{
/// <summary>
/// Name of <see cref="IPolicySource"/> instance, also key in the <see cref="PolicyRegistry"/>.
/// </summary>
string GetName();
/// <summary>
/// The Polly policy, created by the <see cref="IPolicySource"/> implementation.
/// Shall be a singleton instance.
/// </summary>
IsPolicy PollyPolicy { get; }
}
Implementations of IPolicySource commonly have constructors with a logger as parameter. Later, different configurations per instance shall be possible, represented by different GetName() return values. The implementations can have additional members, like TimeSpan GetHttpClientRequestTimeout()
(to have sufficient time for retrying via policy).
I then register the factories for implementations in the ServiceCollection:
public static IServiceCollection AddPolicySource(this IServiceCollection serviceCollection,
Func<IServiceProvider, IPolicySource> policySourceFactory)
{
return serviceCollection.AddSingleton<IPolicySource>(policySourceFactory);
}
which allows code like:
serviceCollection.AddPolicySource(sp =>
new MyPolicySource( // logger from dependency injection
sp.GetRequiredService<ILogger<MyPolicySource>>()));
Multiple IPolicySource implementations, even of the same type and with the same GetName() results, can be registered this way.
Finally, the build of the PolicyRegistry is defined through a factory. The normal AddPolicyRegistry() from the Microsoft extensions registers for the interfaces IPolicyRegistry<string>
and IReadOnlyPolicyRegistry<string>
:
public static IServiceCollection AddPolicyRegistryUsingPolicySources(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IPolicyRegistry<string>>(sp =>
sp.BuildPolicyRegistryWithPolicySources())
.AddSingleton<IReadOnlyPolicyRegistry<string>>(sp =>
sp.GetRequiredService<IPolicyRegistry<string>>());
return serviceCollection;
}
and the IServiceProvider extension method BuildPolicyRegistryWithPolicySources():
public static PolicyRegistry BuildPolicyRegistryWithPolicySources(this IServiceProvider serviceProvider)
{
var policySourcesByName = new Dictionary<string, IPolicySource>();
foreach (var policySource in serviceProvider.GetServices<IPolicySource>())
{
var policySourceName = policySource.GetName();
policySourcesByName[policySourceName] = policySource; // use last, ignore previous with same name.
}
var policyRegistry = new PolicyRegistry();
foreach (var policySourceByName in policySourcesByName)
{
policyRegistry.Add(policySourceByName.Key, policySourceByName.Value.PollyPolicy);
}
return policyRegistry;
}
As of now, it seems to work well. The registration of multiple identical IPolicySource instances follows the rule to always use the last, if multiple with the same name have been registered.
One caveat is, that there is no easy way to register the singleton IPolicySource instances multiple times, e.g. for implementation type or extended interfaces.
serviceCollection.AddHttpClient(...)
and add the policy withhttpClientBuilder.AddPolicyHandlerFromRegistry(MyPolicySource.GetInstanceName())
, so in this case a previously registered policy. I also sethttpClient.Timeout
from an additional property onMyPolicyFactory
(not in theIPolicySource
interface). \$\endgroup\$