HttpClientFactory с регистрацией обработчика политики опроса по умолчанию и внедрением с помощью SimpleInjector

#c# #simple-injector #polly

Вопрос:

За https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory#extending-the-convenience-addtransienthttperrorpolicy-definition Я вижу, что политики добавляются с именем.

 var httpClientOptions = HttpPolicyExtensions
    .HandleTransientHttpError()
    .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
    .WaitAndRetryAsync(6,
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

services.AddHttpClient(/*have to add a name*/)
    .AddPolicyHandler(httpClientOptions);
 

Я использую простой инжектор параллельно с внедрением зависимостей .net, и я внедряю фабрику http-клиента в свои классы-потребители, как показано ниже:

 public PolygonIoApiFetcher(IHttpClientFactory clientFactory)
{
    this.client = clientFactory.CreateClient(/* what to use here */);    
}
 

Моя проблема в том, что у меня есть ряд функций, написанных в других библиотеках, которые не обязательно будут совместно использовать имя политики фабрики клиента между библиотеками.

Кроме того, чтобы все было просто, я, вероятно, хотел бы иметь единую политику повтора poly для всех моих HttpClient , созданных фабрикой — есть ли способ IHttpClientFactory вернуть HttpClient значение по умолчанию с примененной политикой poly по умолчанию?

Я также не хочу использовать типизированную регистрацию для https://github.com/simpleinjector/SimpleInjector/issues/654, и мне нужно контролировать время жизни объекта с помощью IHttpClientFactory введенного, а не HttpClient вводимого в класс-потребитель.

Таким образом, подключение/ввод при запуске должны определять политику, введенную и легко перенастроенную/измененную, а не потребляющим классом.

Я предоставил то, что я сделал до сих пор, но указатели ценят, если это, как я подозреваю, уже решенная проблема.

Идея ниже — создайте класс-оболочку

Метод HttpClient CreateClient(this IHttpClientFactory factory) расширения без параметров из System.Net.Http не может быть переопределен, поэтому я решил использовать класс-оболочку и вручную выбрать политику во время внедрения:

 public interface IPollyHttpClientFactory
{
    HttpClient CreateClient();
}

public class PollyHttpClientFactoryWrapper
{
    private readonly string policyName;
    private readonly Container container;

    public PollyHttpClientFactoryWrapper(
        string policyName, Container container)
    {
        this.policyName = policyName ?? throw new ArgumentNullException();
        this.container = container ?? throw new ArgumentNullException();
    }

    public HttpClient CreateClient()
    {
        return this.container
            .GetInstance<IHttpClientFactory>()
            .CreateClient(this.policyName);
    }
}
 

И использовать во время регистрации:

 Container.RegisterSingleton<IHistoricalAggregates>(
    () => new PolygonIoApiFetcher(
        new PollyHttpClientFactoryWrapper("RetryPolicy", Container),
        Container.GetInstance<ApiKeys>().PolygonIoApiKey));
 

Единственная «проблема» в том, что мои классы должны будут использовать IPollyHttpClientFactory интерфейс, но я могу с этим смириться.

Ответ №1:

Я не уверен, что смогу дать вам удовлетворительный ответ, но вот идея для возможной реализации. Вы можете сделать IPollyHttpClientFactory это условно, таким образом, чтобы каждый потребитель получал свою собственную версию. Это можно сделать с помощью RegisterConditional метода. Например:

 var container = new Container();

container.RegisterSingleton<IHistoricalAggregates, PolygonIoApiFetcher>();

container.RegisterConditional(
    typeof(IPollyHttpClientFactory),
    c => typeof(PollyHttpClientFactory<>)
             .MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    c => true);

container.MapPolicy<PolygonIoApiFetcher>("RetryPolicy");
 

Здесь MapPolicy приведен пользовательский метод расширения:

 public static class ContainerPolicyExtensions
{
    public static void MapPolicy<TImplementation>(
        this Container container, string policyName) =>
        container.RegisterInstance(new Policy<TImplementation>(policyName));
}
 

Policy<> это простой универсальный объект данных, который позволяет хранить имя политики для использования HttpClient (вы можете добавить любые дополнительные данные, относящиеся к созданию http-клиентов, в этот класс политики).:

 public sealed record Policy<TConsumer>(string PolicyName);
 

PollyHttpClientFactory<> является общей реализацией IPollyHttpClientFactory . В него вводится соответствующая Policy<T> информация, и поэтому он может создать http-клиент, специфичный для своего потребителя:

 public record PollyHttpClientFactory<TConsumer>(
    IHttpClientFactory Factory, Policy<TConsumer> Policy)
    : IPollyHttpClientFactory
{
    public HttpClient CreateClient() =>
        this.Factory.CreateClient(Policy.PolicyName);
}
 

При желании вы можете объединить методы Register и MapPolicy в один метод расширения:

 public static void RegisterWithPolicy<TService, TImplementation>(
    this Container container, string policyName, Lifestyle lifestyle = null)
    where TService : class
    where TImplementation : class, TService
{
    if (lifestyle is null) container.Register<TService, TImplementation>();
    else container.Register<TService, TImplementation>(lifestyle);
    container.MapPolicy<TImplementation>(policyName);
}
 

Это позволяет вам выполнить регистрацию и сопоставление одним махом:

 container.RegisterWithPolicy<IHistoricalAggregates, PolygonIoApiFetcher>(
    "RetryPolicy",
    Lifestyle.Singleton);
 

И последнее замечание. В своем вопросе вы вызываете IHttpClientFactory.CreateClient метод из конструктора и сохраняете клиента в потребителе. Как правило, это не очень хорошая идея, потому что это может позволить клиенту жить в течение длительного времени и противоречит цели IHttpClientFactory . Вместо этого сохраните IPollyHttpClientFactory (или IHttpClientFactory ) в закрытом поле и вызывайте его CreateClient метод только из методов потребителя и удаляйте клиента в конце метода.