使用Polly结合IDistributedCache与IHttpClientFactory时出现类型转换运行时错误的问题咨询
解决Polly缓存策略与IHttpClientFactory的类型不匹配问题
你遇到的错误完全是因为类型不匹配导致的:你注册的AsyncCachePolicy<string>是针对字符串类型的缓存策略,但IHttpClientFactory要求的策略必须是处理HttpResponseMessage的IAsyncPolicy<HttpResponseMessage>,两者泛型参数不一致,自然无法转换。
要实现「重试+超时+缓存成功响应」的目标,我们需要调整缓存策略的类型,让它适配HttpClient的返回值,再把多种策略组合起来。下面是具体的解决方案:
1. 调整缓存策略为针对HttpResponseMessage
首先,我们需要创建一个处理HttpResponseMessage的缓存策略,而不是string。因为直接缓存HttpResponseMessage需要序列化/反序列化,所以我们要自定义序列化逻辑:
第一步:实现HttpResponseMessage的缓存序列化器
Polly默认不支持直接序列化HttpResponseMessage,我们需要自己实现IAsyncCacheSerializer<HttpResponseMessage>来处理响应的序列化和反序列化:
public class HttpResponseCacheSerializer : IAsyncCacheSerializer<HttpResponseMessage> { private readonly JsonSerializerOptions _jsonOptions; public HttpResponseCacheSerializer(JsonSerializerOptions jsonOptions = null) { _jsonOptions = jsonOptions ?? new JsonSerializerOptions { PropertyNameCaseInsensitive = true, WriteIndented = false }; } public async Task<byte[]> SerializeAsync(HttpResponseMessage item, CancellationToken cancellationToken = default) { // 提取响应中需要缓存的内容:状态码、头部、响应体 var cachedData = new CachedHttpResponse { StatusCode = (int)item.StatusCode, Headers = item.Headers.ToDictionary(h => h.Key, h => h.Value.FirstOrDefault()), Content = await item.Content.ReadAsStringAsync(cancellationToken) }; return JsonSerializer.SerializeToUtf8Bytes(cachedData, _jsonOptions); } public async Task<HttpResponseMessage> DeserializeAsync(byte[] bytes, CancellationToken cancellationToken = default) { var cachedData = JsonSerializer.Deserialize<CachedHttpResponse>(bytes, _jsonOptions); if (cachedData == null) return null; // 重建HttpResponseMessage var response = new HttpResponseMessage((HttpStatusCode)cachedData.StatusCode); response.Content = new StringContent(cachedData.Content); // 恢复响应头部 foreach (var header in cachedData.Headers) { if (!string.IsNullOrEmpty(header.Value)) { response.Headers.TryAddWithoutValidation(header.Key, header.Value); } } return response; } // 用于序列化的中间类 private class CachedHttpResponse { public int StatusCode { get; set; } public Dictionary<string, string> Headers { get; set; } = new(); public string Content { get; set; } = string.Empty; } }
第二步:创建适配HttpResponseMessage的缓存策略
现在我们可以用这个序列化器创建缓存策略,同时指定缓存键生成规则(确保每个请求的缓存键唯一)和缓存过滤条件(只缓存成功响应):
// 获取分布式缓存并转换为Polly的AsyncCacheProvider var distributedCache = serviceProvider.GetRequiredService<IDistributedCache>(); var cacheProvider = distributedCache.AsAsyncCacheProvider<HttpResponseMessage>(); // 创建缓存策略 var cachePolicy = Policy.CacheAsync<HttpResponseMessage>( cacheProvider: cacheProvider, expiry: TimeSpan.FromSeconds(30), // 根据请求的方法和URL生成唯一缓存键 keyStrategy: new CacheKeyStrategy(context => { var request = context.GetHttpRequestMessage(); return $"{request.Method.Method}_{request.RequestUri}"; }), // 使用自定义的序列化器 resultSerializer: new HttpResponseCacheSerializer(), // 只缓存成功的响应(状态码2xx) resultFilter: context => context.Result.IsSuccessStatusCode );
2. 组合重试、超时和缓存策略
接下来把重试、超时和缓存策略组合成一个复合策略,注意策略的顺序:缓存应该放在最外层,这样重试后的成功结果会被缓存,避免重复请求:
// 重试策略:针对5xx错误和网络异常重试3次,使用指数退避 var retryPolicy = Policy .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode && r.StatusCode >= HttpStatusCode.InternalServerError) .Or<HttpRequestException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 超时策略:10秒超时 var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)); // 组合策略:缓存 -> 重试 -> 超时(执行顺序是从内到外:先超时,再重试,最后缓存) var combinedPolicy = Policy.WrapAsync(cachePolicy, retryPolicy, timeoutPolicy); // 将组合策略注册到Polly的策略注册表 registry.Add("CombinedHttpPolicy", combinedPolicy);
3. 修正PolicySelector的逻辑
最后确保PolicySelector从注册表中获取的是正确类型的组合策略:
private IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry, HttpRequestMessage httpRequestMessage) { return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("CombinedHttpPolicy"); }
这样调整后,你的HttpClient就能同时使用重试、超时和缓存策略,而且不会再出现类型转换的错误了。
内容的提问来源于stack exchange,提问作者O.O
相关产品推荐
相关产品推荐

