Search code examples
c#caching.net-6.0pollyretry-logic

Polly with IDistributedCache and IHttpClientFactory Policy


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

Solution

  • 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.

    How to fix it?

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