ASP.NET Web API exposes all the goodness of HTTP. Caching is an important feature of HTTP and ASP.NET Web API allows for building services and clients that take advantage of this feature. I, along with a few friends in the Web API community, have been busy building caching extensions for Web API in a project called CacheCow which is hosted on GitHub.
CacheCow has two separate components: server and client. These will be used independently by service providers (server) and their consumers (client).
Server component allows for easy handling of HTTP caching scenarios on server by generating ETag, responding to validation of cache (see earlier posts on this subject, especially this and for full list here), cache invalidation and storage of cache metadata. Storage of cache metadata is possible in various stores, currently in-memory and SQL Server have been implemented and RavenDB and Redis is on the pipeline [UPDATE: RavenDB is implemented and NuGet package is available here]. Since storage has been abstracted away, any storage mechanism can be plugged in without making any server changes.
Client component looks after making cache-aware requests, cache validation and cache storage. Currently in-memory and file-based storage is available but other stores such as Redis, SQL Server, MongoDB and RavenDB are in the pipeline. Since storage has been abstracted away, any storage mechanism can be plugged in without making any client changes. One of important features of storage is total and per-site quota.
It is important to note that while clients can be browsers or native Apps (WPF, Silverlight, iOS, Android, etc), arguably more often than not they will be server components themselves. For example, an ASP.NET web site can call services of an ASP.NET Web API server. Also middleware components could similarly use resources exposed by Web API. As such it is very crucial that cache storage solutions are performant, scalable and configurable.
In this post, I will look into CacheCow.Client a little but more. For more info, you can read previous posts on the topic in this blog.
CacheCow.Client alternatives
The only alternative to CacheCow.Client (that I am aware of) is using WinINET caching. Internet Explorer also uses this so the cache store will be the same. This is basically windows' HTTP request stack which has been exposed in .NET Framework since v 2.0 through WebRequest:
As you can see, we can define a cache policy which will be applied to the request and according to the policy, Internet Explorer cache is used. Cache policy has a few possible values that are defined here. Notable values include:
Basically in order to use WinINET caching with the new Web API stack, you need to create an HttpClient but provide WebRequestHandler as the MessageHandler:
Using this feature, you can enable caching with little coding on the client.
Here are a few advantages of CacheCow.Client over WinINET (or rather disadvantages of WinINET):
RequestCachePolicy policy = new RequestCachePolicy( RequestCacheLevel.Default); WebRequest request = WebRequest.Create(uri); request.CachePolicy = policy; WebResponse response = request.GetResponse();
As you can see, we can define a cache policy which will be applied to the request and according to the policy, Internet Explorer cache is used. Cache policy has a few possible values that are defined here. Notable values include:
- CacheOnly: retrieves the request only from cache
- BypassCache: does not use cache at all and goes straight to the server
- CacheIfAvailable: retrieves from local or intermediate cache if resource available otherwise retrieve from server
- Default: Similar to previous but current cache policy takes effect
This same mechanism is now exposed in HttpClient but basically is built on the top of WebRequest. Henrik fully covers this feature in his blog here.
Basically in order to use WinINET caching with the new Web API stack, you need to create an HttpClient but provide WebRequestHandler as the MessageHandler:
HttpClient client = new HttpClient(new WebRequestHandler() { CachePolicy = new RequestCachePolicy( RequestCacheLevel.Default) }); // this is a sample. It is not advised to use .Result since can lead to deadlock! var httpResponseMessage = client.GetAsync("http://carmanager.softxnet.co.uk/api/car/3").Result; var httpResponseMessage2 = client.GetAsync("http://carmanager.softxnet.co.uk/api/car/3").Result;
Using this feature, you can enable caching with little coding on the client.
Why I would choose CacheCow.Client rather than WinINET
Because it goes to 11! As we saw, it is very easy to get started with caching in HttpClient. But as we noted, it is very likely that HttpClient could be used in a server context hence having a reliable and scalable solution is very important in production.
1. Caching will be shared with Internet Explorer
In a production scenario, you need an implementation which is predictable and reliable. If someone uses Internet Explorer on the machine, storage area for your application's resources will be taken by just simple browsing. This can lead Internet Explorer to flush application resources in order to store resources for the browsing session.
2. You have little control over quota
With CacheCow.Client, you can define a global and a per-site quota for storage of resources while such feature is not accessible (although there could be some registry entries for changing these variables) in WinINET caching. Also these variables could be overwritten by installation of a newer version of Internet Explorer.
3. Cache is local to the machine and cannot be shared across servers
In a production scenario, it is desirable to be able to store caches in a central store so network traffic and requests could be limited while with WinINET caching, each server will use its own local cache store.
4. WinINET is file-based
With WinINET, cache is stored in a file location while for a high-throughput production environment, robust caching using solutions such as Redis is required. CacheCow client by abstracting the storage can use any number of storage mechanisms such as Redis, MongoDB, RavenDB, etc.
5. CachePolicy is global for the HttpClient instance
Sometimes you might need to bypass caching. With WinINET, this has to be done with changing policy at the client level which applies across all requests for that HttpClient while CacheCow.Client respects will not use cached resources if you set CacheControl header of the request to no-cache. This basically recommended implementation based on HTTP specification (RFC2616).
6. With WinINET you do not know if request was retrieved from cache
With WinINET, there is no way to tell if response was retrieved from the cache or origin server. CacheCow.Client provides x-cachecow header which provides various information which can be used for debugging and troubleshooting scenarios.
Introducing CacheCow.Client.FileCacheStore
Last week I finished first version of a persistent cache store which is file based. This is available using NuGet and (package name is CacheCow.Client.FileCacheStore) and the code available at GitHub.
Using this persistent store is very easy. After getting the package from NuGet, create an HttpCient while as a delegating handler, pass CachingHandler (covered before here) while setting the store to a new instance of FileStore. While creating a FileStore, you need to specify a folder for storing the cached resources:
var httpClient = new HttpClient( new CachingHandler( new FileStore("c:\\Cache")) { InnerHandler = new HttpClientHandler() });
That is all you have to do! Now all your requests will store cacheable resources in a file-based persistent store.
Currently for quota it uses default values but I am in the process of exposing values so you can configure quota.
CacheCow roadmap
After exposing quota settings, I will be working on CacheCow.Client.RedisCacheStore for a high throughput production level cache storage.Please keep me posted by your comments, feedback and raising bug/issues on the GitHub page. You are awesome!