Polly 与 IDistributedCache 和 IHttpClientFactory 策略

Polly with IDistributedCache and IHttpClientFactory Policy

使用以下代码可以正常编译,但收到以下运行时错误。似乎在使用 IHttpClientFactory 时仅支持 HttpResponseMessage 的策略之间存在冲突?
最终目标是能够使用多种策略,如重试、超时等,如果一切正常,使用缓存策略缓存结果...

Unable to cast object of type 'Polly.Caching.AsyncCachePolicy'1[System.String]' to type 'Polly.IAsyncPolicy'1[System.Net.Http.HttpResponseMessage]'.'

serviceCollection.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "...";
});
IPolicyRegistry<string> registry = serviceCollection.AddPolicyRegistry();
var cacheProvider = ServiceProvider.GetRequiredService<IDistributedCache>().AsAsyncCacheProvider<string>();
serviceCollection.AddSingleton(serviceProvider => cacheProvider);

AsyncCachePolicy<string> cachePolicy =
Policy.CacheAsync(
    cacheProvider: cacheProvider,
    TimeSpan.FromSeconds(30));
registry.Add("CachingPolicy", cachePolicy);

serviceCollection.AddHttpClient<IMyClient, MyClient>()
    .AddPolicyHandlerFromRegistry(this.PolicySelector)
    
private IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry, HttpRequestMessage httpRequestMessage)
{
    return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("CachingPolicy");
}

如错误所述,您无法将 AsyncCachePolicy<string> 转换为 IAsyncPolicy<HttpResponseMessage>。由于所有 AsyncXYZPolicy 都实现了 IAsyncPolicy 接口,这就是问题不是出自此处的原因。而不是来自类型参数。

AddHttpClient return 是 IHttpClientBuilder。它们上面有 several extension methods,例如 AddPolicyHandlerFromRegistryAddTransientHttpErrorPolicyAddPolicyHandler。在所有情况下,您都需要注册 return 类型为 HttpResponseMessage.

的策略

如果您尝试直接通过 AddPolicyHandler 注册您的缓存策略,那么它会导致编译错误而不是 run-time 错误。但是因为您从注册表中动态检索策略,这就是它在运行时抛出异常的原因。

如何解决?

与其将策略定义为 AsyncCachePolicy<string>,不如将其定义为 AsyncCachePolicy<HttpResponseMessage>。为此,您需要更改 AsAsyncCacheProvider 方法的类型参数。

var cacheProvider = ServiceProvider
   .GetRequiredService<IDistributedCache>()
   .AsAsyncCacheProvider<HttpResponseMessage>();

您还需要更改 cachePolicy 的类型

AsyncCachePolicy<HttpResponseMessage> cachePolicy =
Policy.CacheAsync(
    cacheProvider: cacheProvider,
    TimeSpan.FromSeconds(30));

旁注:我还建议将策略注册表项 ("CachingPolicy") 存储在常量中,并在注册和检索时引用它。


更新 #1:

我什至不确定您是否必须调用 AsAsyncCacheProvider 方法。让我仔细检查一下。


更新#2

在阅读 AsAsyncCacheProvider 的源代码后,我意识到它只支持 byte[]string 作为类型参数。这表明您不能使用此处的 AddPolicyHandler 方法来自动缓存响应。

相反,您必须在 MyClient 实现中直接使用 AsyncPolicyCache<string>。您需要修改 MyClient 的构造函数以接收 IReadonlyPolicyRegister<string> 参数。

private readonly IAsyncPolicy<string> _cachePolicy;
public MyClient(HttpClient client, IReadOnlyPolicyRegistry<string> policyRegistry)
{
    _cachePolicy = policyRegistry.Get<IAsyncPolicy<string>>("CachingPolicy");
    // ...
}

并且在您公开的方法中,您需要显式使用 ExecuteAsync

await _cachePolicy.ExecuteAsync(context => getXYZ(), new Context("XYZUniqueKey"));

getXYZ 需要 return 一个字符串(可能是响应主体)。