具有默认 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);
}
您可以选择将 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
方法,并在方法末尾处理客户端。
根据 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);
}
您可以选择将 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
方法,并在方法末尾处理客户端。