Caching in Polly and the HttpClientFactory
Full source code here.
Want to learn more about Polly? Check out my Pluralsight course on it.
Polly allows you to cache a response for reuse by a subsequent request, it supports both local an distributed caches, full information can be found here https://github.com/App-vNext/Polly/wiki/Cache.
The HttpClientFactory
lets you define polices in startup.cs
and apply them to HttpClient
requests anywhere in your application. With most policies this makes it easier for you to apply a policy to a request, but with the cache policy it complicates matters.
With the release of HttpClientFactory
there are now two ways to use the cache policy, one taking advantage of HttpClientFactory
to apply the policy to requests, and the other bypassing it when executing requests.
Caching with the cache policy inside the HttpClientFactory
First let’s look at how to use the Cache Policy using PolicySelector
method on the HttpClientFactory
to apply the policy to the request, see this post for more info.
If you are working in Polly V6+ add Polly.Caching.Memory
package to your project, this is important, don’t add Polly.Caching.MemoryCache
that is for Polly V5.9 and earlier, and many an hour was lost figuring that out.
In Startup.cs
add the following to the ConfigureServices
method -
1public void ConfigureServices(IServiceCollection services)
2{
3 services.AddMemoryCache();
4 services.AddSingleton<IAsyncCacheProvider, MemoryCacheProvider>();
5
6 IPolicyRegistry<string> registry = services.AddPolicyRegistry();
7
8 services.AddHttpClient("RemoteServer", client =>
9 {
10 client.BaseAddress = new Uri("http://localhost:5000/api/");
11 client.DefaultRequestHeaders.Add("Accept", "application/json");
12 }).AddPolicyHandlerFromRegistry(PolicySelector);
This sets up the in memory cache and policy registry, adding both to ServicesCollection
. The cache policy is not added here, instead it is added in the Configure(..)
method (thanks Andrew Lock for reminding me about this).
Next, add the HttpClientFactory
and a method to pick the appropriate policy from the registry.
Inside the Configure(..)
I setup the cache policy. The registry and cache provider are passed via dependency injection.
1public void Configure(IApplicationBuilder app, IHostingEnvironment env,
2 IAsyncCacheProvider cacheProvider, IPolicyRegistry<string> registry)
3{
4 CachePolicy<HttpResponseMessage> cachePolicy =
5 Policy.CacheAsync<HttpResponseMessage>(cacheProvider, TimeSpan.FromSeconds(30));
6 registry.Add("CachingPolicy", cachePolicy);
In the controller I have a constructor that takes one argument, the HttpClientFactory
.
1public CatalogController(IHttpClientFactory httpClientFactory)
2{
3 _httpClientFactory = httpClientFactory;
4}
In the action method I get the HttpClient
from the HttpClientFactory
and specify the remote endpoint I want to call, but now I can’t use usual httpClient.GetAsync(..)
because I can’t pass it a cache context key (i.e. the name of where I look for previously cached responses and store new responses).
Instead I build a HttpRequestMessage
, specifying the HttpMethod
, Uri
and using a Polly extension method I set the cache context.
1HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get,
2 new System.Uri(httpClient.BaseAddress + requestEndpoint));
3
4httpRequestMessage.SetPolicyExecutionContext(new Context($"GetInventoryById-{id}"));
5
6HttpResponseMessage response = await httpClient.SendAsync(httpRequestMessage);
This approach feels a bit awkward and not many people use the HttpRequestMessage
to send requests to remote services.
Caching with the cache policy outside the HttpClientFactory
An alternative to the above is to add the cache to the registry, but not add the registry to the HttpClientFactory
.
In this case the adding the HttpClientFactory
to the services collection changes slightly -
1services.AddHttpClient("RemoteServer", client =>
2{
3 client.BaseAddress = new Uri("http://localhost:5000/api/");
4 client.DefaultRequestHeaders.Add("Accept", "application/json");
5}); // no policy selector
In the controller we now pass in the registry as HttpClientFactory
as constructor arguments.
1public StockMgtController(IHttpClientFactory httpClientFactory, IPolicyRegistry<string> policyRegistry)
2{
3 _httpClientFactory = httpClientFactory;
4 _policyRegistry = policyRegistry;
5}
There are changes inside the action method also -
1[HttpGet("{id}")]
2public async Task<IActionResult> Get(int id)
3{
4 string requestEndpoint = $"inventory/{id}";
5 var httpClient = _httpClientFactory.CreateClient("RemoteServer");
6
7 var cachePolicy = _policyRegistry.Get<CachePolicy<HttpResponseMessage>>("CachingPolicy");
8 Context policyExecutionContext = new Context($"GetInventoryById-{id}");
9
10 HttpResponseMessage response = await cachePolicy.ExecuteAsync(
11 context => httpClient.GetAsync(requestEndpoint), policyExecutionContext);
12 //snip..
The httpClient
is retrieved from the HttpClientFactory
as before, but now the cachePolicy
is taken from the policy registry, the Context
is defined and then we use the cachePolicy.ExecuteAsync(..)
method to make call the httpClient.GetAsync(..)
method.
The other way
You might have noticed that there is another way of using the cache policy. You could use the HttpClientFactory
and a PolicySelector
method to apply most policies to your HttpClient
requests, but not for the cache policy. I wouldn’t do this, it will confuse everyone.
Conclusion
The outcome of both is the same, I think the first approach is better, even though the call to the httpClient
is a little more convoluted, it will be more consistent with how you call other policies in your application, there is also a chance the Polly team will add an extension method to make the use of HttpRequestMessage
transparent to us developers.
Full source code here.