具有默认 Polly 策略处理程序注册和注入的 HttpClientFactory SimpleInjector

HttpClientFactory with default Polly policy handler registration and injection with SimpleInjector

根据 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);

我正在使用 Simple Injector 与 .net 依赖项注入并行,并将 http 客户端工厂注入到我的消费 classes 中,如下所示:

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

我的问题是我在其他库中编写了一些函数,这些函数不一定会在库之间共享客户端工厂策略名称。

此外,为了简单起见,我可能希望为工厂创建的所有 HttpClient 设置一个多边形重试策略 - 有没有办法让 IHttpClientFactory 到 return a默认 HttpClient 应用默认多边形策略?

我不想根据 https://github.com/simpleinjector/SimpleInjector/issues/654 使用类型化注册,我需要通过注入的 IHttpClientFactory 来控制对象的生命周期,而不是 HttpClient注入消费 class.

总而言之,启动时的 wiring/injection 应该确定注入的策略并且很容易 rewired/changed,而不是由消费 class.

我已经提供了到目前为止所做的工作,但如果我怀疑这是一个已经解决的问题,请多多指教。

下面的想法 - 创建一个包装器 class

无法覆盖 System.Net.Http 中的无参数 HttpClient CreateClient(this IHttpClientFactory factory) 扩展方法,所以我想使用包装器 class 并在注入期间手动 select 策略:

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));

唯一的“问题”是我的 classes 需要使用 IPollyHttpClientFactory 界面,但我可以接受。

我不确定我能否给你一个满意的答案,但这里有一个可能的实现想法。您可以使 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 客户端相关的任何其他数据 class):

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);
}

您可以选择将 RegisterMapPolicy 方法合并为一个扩展方法:

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 方法,并在方法末尾处理客户端。