Using the following code compiles fine, but receives the runtime error below. Seems to be a conflict between the policy only supporting HttpResponseMessage
when using IHttpClientFactory
?
The end goal is to be able to use several policies like retry, timeout, etc. and if everything is OK cache the result with the cache policy...
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");
}
As the error says you can not convert AsyncCachePolicy<string>
to IAsyncPolicy<HttpResponseMessage>
. Since all AsyncXYZPolicy
implements the IAsyncPolicy
interface that's why the problem is not coming from here. Rather than from the type parameter.
The AddHttpClient
returns an IHttpClientBuilder
. There are several extension methods on them like AddPolicyHandlerFromRegistry
, AddTransientHttpErrorPolicy
or AddPolicyHandler
. In all cases you need to register a policy where the return type is HttpResponseMessage
.
If you would try to register your cache policy directly via the AddPolicyHandler
then it would cause compilation error rather than run-time error. But because you retrieve the policy dynamically from the registry that's why it throws exception at runtime.
Rather than defining a policy as AsyncCachePolicy<string>
you should define it as AsyncCachePolicy<HttpResponseMessage>
. To do that you need to change the type parameter of the AsAsyncCacheProvider
method.
var cacheProvider = ServiceProvider
.GetRequiredService<IDistributedCache>()
.AsAsyncCacheProvider<HttpResponseMessage>();
You also need to change the cachePolicy
's type
AsyncCachePolicy<HttpResponseMessage> cachePolicy =
Policy.CacheAsync(
cacheProvider: cacheProvider,
TimeSpan.FromSeconds(30));
Side note: I would also suggest to store the policy registry key ("CachingPolicy"
) in a constant and refer to it when you register that and when you retrieve that.
UPDATE #1:
I'm not even sure that you have to call the AsAsyncCacheProvider
method at all. Let me double check.
UPDATE #2
After reading the source code of the AsAsyncCacheProvider
I've realized that it only supports byte[]
or string
as a type parameter. This indicates that you can't use the AddPolicyHandler
methods here to automatically cache the response.
Rather you have to use the AsyncPolicyCache<string>
directly in your MyClient
implementation. You need to modify the constructor of the MyClient
to recieve an IReadonlyPolicyRegister<string>
parameter as well.
private readonly IAsyncPolicy<string> _cachePolicy;
public MyClient(HttpClient client, IReadOnlyPolicyRegistry<string> policyRegistry)
{
_cachePolicy = policyRegistry.Get<IAsyncPolicy<string>>("CachingPolicy");
// ...
}
And inside your exposed method you need to explicitly use ExecuteAsync
await _cachePolicy.ExecuteAsync(context => getXYZ(), new Context("XYZUniqueKey"));
The getXYZ
needs to return a string (potentially the response body).