Using ASP.NET Core 3, I have a library providing access to a web API that registers IHttpClient like this
services.AddHttpClient<OntraportHttpClient>();
services.AddHttpClient<IOntraportPostForms, OntraportPostForms>();
I do not think it would be good practice to make that library dependent on Polly, so Polly must be configured in the ASP.NET application using it.
Normally, I would register it this way, but it doesn't work since it is already registered.
services.AddHttpClient<OntraportHttpClient>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
Calling the library to register itself after registering the IHttpClient class won't work either because there's no TryAddHttpClient method.
What's the recommended approach to deal with this kind of situation?
Also, in my case, the library is using 2 IHttpClient to access 2 different APIs, do I have to configure Polly twice?
Afaik, the Microsoft implementation of HttpClientFactory does not offer a direct API to edit/append-to an already-defined logical HttpClient
(something like services.AppendToHttpClientDefinition<T>(...)
). You could ask on the relevant Microsoft github repo.
Three possible approaches to the overall goal:
OntraportHttpClient
allow extra config to be passed inExtend the way the library (this?) registers OntraportHttpClient
so that external code can pass in an Action<IHttpClientBuilder> additionalConfig
.
The registering code in the library would then be:
var ontraportHttpClientbuilder = services.AddHttpClient<OntraportHttpClient>();
additionalConfig(ontraportHttpClientbuilder);
The library would have no dependency on Polly, but the Action<IHttpClientBuilder> additionalConfig
you pass in can configure Polly policies. Outside the library you can declare and pass in:
Action<IHttpClientBuilder> additionalConfig = (builder) => builder.AddTransientHttpErrorPolicy(...);
If you prefer not to add any extra public configuration hook to OntraportApi.NET
, you could:
2a. Use IConfigureNamedOptions<HttpClientFactoryOptions>
outside the library, to dynamically change the configuration for OntraportHttpClient
, something like:
services.AddSingleton<IConfigureOptions<HttpClientFactoryOptions>, DynamicHttpClientFactoryConfiguration>();
where:
internal class DynamicHttpClientFactoryConfiguration : IConfigureNamedOptions<HttpClientFactoryOptions>
{
public void Configure(String httpClientName, HttpClientFactoryOptions options)
{
if (httpClientName == nameof(OntraportHttpClient))
{
options.HttpMessageHandlerBuilderActions.Add(httpMessageHandlerBuilder =>
{
httpMessageHandlerBuilder.AdditionalHandlers.Add(pollyDelegatingHandler);
});
}
}
}
where pollyDelegatingHandler
is some PolicyHttpMessageHandler you have constructed separately.
Or very similarly
2b. Use an IHttpMessageHandlerBuilderFilter
, eg:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, DynamicMessageHandlerBuilderFilter>());
where:
internal class DynamicMessageHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter
{
public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
{
return (builder) =>
{
next(builder);
if (builder.Name == nameof(OntraportHttpClient))
{
builder.AdditionalHandlers.Add(pollyDelegatingHandler);
}
};
}
}
Polly can be used with anything; it doesn't have to be used only within HttpClient
s via HttpClientFactory
. So you could
var foo = await policy.ExecuteAsync<Foo>(async () => await ontraportClient.GetAsync<Foo>(/* etc */));
for some IAsyncPolicy policy
.
From a quick read of OntraportHttpClient
, a pattern like this should work, but it is marginally less efficient (and won't work for co-operative cancellation eg by TimeoutPolicy, if OntraportHttpClient
doesn't support CancellationToken
s).