At the very first, “Happy New Year ” to all. In this article we will learn to implement Output Caching in Web API applications.
This article assumes familiarity with basic caching concepts and a good understanding of the Web API of MVC based applications. If you are very new to both concepts then my recommendation is to first learn those concepts then proceed with this article.
Most probably you have implemented caching in ASP.NET and the MVC Architecture. They are pretty easy. In the case of MVC just plug in the “[OutputCache]” attribute over action and it is done. And in the case of a form-based application we have implemented Output Caching by setting a proper page directive.
But when the scenario is Web API , the situation is a little more complex. Unfortunately the Web API doesn’t have built-in support to implement caching. But we can implement the caching functionality by implementing our own class.
First of all install the following package in your Web API application.
If you are running .NET 4.5 then go for this package.
- Install-Package Strathweb.CacheOutput.WebApi2
For .NET 4.5 , install the following one:
- Install-Package Strathweb.CacheOutput
Now we need to implement our own caching class that will be derived from the ActionFilterAttribute class. If you have worked with various filter actions then most probably you are already familiar with this class. So, let’s proceed step-by-step.
Step 1: Derive a class from the ActionFilterAttribute class
- public class WebApiOutputCacheAttribute : ActionFilterAttribute
- {
- }
Step 2: Set the properties for the class:
- private int _timespan;
- // client cache length in seconds
- private int _clientTimeSpan;
- // cache for anonymous users only?
- private bool _anonymousOnly;
- // cache key
- private string _cachekey;
- // cache repository
- private static readonly ObjectCache WebApiCache = MemoryCache.Default;
Step 3: Set the following constructor in the class:
- public WebApiOutputCacheAttribute(int timespan, int clientTimeSpan, bool anonymousOnly)
- {
- _timespan = timespan;
- _clientTimeSpan = clientTimeSpan;
- _anonymousOnly = anonymousOnly;
- }
Step 4: Check whether or not the request should be cached
In this step we will implement one private function that will determine whether or not the request should be cached.
- private bool _isCacheable(HttpActionContext ac)
- {
- if (_timespan > 0 && _clientTimeSpan > 0)
- {
- if (_anonymousOnly)
- if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
- return false;
- if (ac.Request.Method == HttpMethod.Get) return true;
- }
- Else
- {
- throw new InvalidOperationException("Wrong Arguments");
- }
- return false;
- }
Step 5: Function to enable client-side caching.
This is the function that will enable client-side caching:
- private CacheControlHeaderValue setClientCache()
- {
- var cachecontrol = new CacheControlHeaderValue();
- cachecontrol.MaxAge = TimeSpan.FromSeconds(_clientTimeSpan);
- cachecontrol.MustRevalidate = true;
- return cachecontrol;
- }
- public override void OnActionExecuting(HttpActionContext ac)
- {
- if (ac != null)
- {
- if (_isCacheable(ac))
- {
- _cachekey = string.Join(":", new string[] { ac.Request.RequestUri.AbsolutePath, ac.Request.Headers.Accept.FirstOrDefault().ToString() });
- if (WebApiCache.Contains(_cachekey))
- {
- var val = (string)WebApiCache.Get(_cachekey);
- if (val != null)
- {
- ac.Response = ac.Request.CreateResponse();
- ac.Response.Content = new StringContent(val);
- var contenttype = (MediaTypeHeaderValue)WebApiCache.Get(_cachekey + ":responsect");
- if (contenttype == null)
- contenttype = new MediaTypeHeaderValue(_cachekey.Split(':')[1]);
- ac.Response.Content.Headers.ContentType = contenttype;
- ac.Response.Headers.CacheControl = setClientCache();
- return;
- }
- }
- }
- }
- Else
- {
- throw new ArgumentNullException("actionContext");
- }
- }
Step 7: Override the OnActionExecuted function from the ActionAttributeFilter class.
- public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
- {
- if (!(WebApiCache.Contains(_cachekey)))
- {
- var body = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
- WebApiCache.Add(_cachekey, body, DateTime.Now.AddSeconds(_timespan));
- WebApiCache.Add(_cachekey + ":response-ct",
- ecutedContext.Response.Content.Headers.ContentType, DateTime.Now.AddSeconds(_timespan));
- }
- if (_isCacheable(actionExecutedContext.ActionContext))
- actionExecutedContext.ActionContext.Response.Headers.CacheControl = setClientCache();
- }
- public class WebApiOutputCacheAttribute : ActionFilterAttribute
- {
- private int _timespan;
- // client cache length in seconds
- private int _clientTimeSpan;
- // cache for anonymous users only?
- private bool _anonymousOnly;
- // cache key
- private string _cachekey;
- // cache repository
- private static readonly ObjectCache WebApiCache = MemoryCache.Default;
- public WebApiOutputCacheAttribute(int timespan, int clientTimeSpan, bool anonymousOnly)
- {
- _timespan = timespan;
- _clientTimeSpan = clientTimeSpan;
- _anonymousOnly = anonymousOnly;
- }
- private bool _isCacheable(HttpActionContext ac)
- {
- if (_timespan > 0 && _clientTimeSpan > 0)
- {
- if (_anonymousOnly)
- if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
- return false;
- if (ac.Request.Method == HttpMethod.Get) return true;
- }
- else
- {
- throw new InvalidOperationException("Wrong Arguments");
- }
- return false;
- }
- private CacheControlHeaderValue setClientCache()
- {
- var cachecontrol = new CacheControlHeaderValue();
- cachecontrol.MaxAge = TimeSpan.FromSeconds(_clientTimeSpan);
- cachecontrol.MustRevalidate = true;
- return cachecontrol;
- }
- public override void OnActionExecuting(HttpActionContext ac)
- {
- if (ac != null)
- {
- if (_isCacheable(ac))
- {
- _cachekey = string.Join(":", new string[] { ac.Request.RequestUri.AbsolutePath, ac.Request.Headers.Accept.FirstOrDefault().ToString() });
- if (WebApiCache.Contains(_cachekey))
- {
- var val = (string)WebApiCache.Get(_cachekey);
- if (val != null)
- {
- ac.Response = ac.Request.CreateResponse();
- ac.Response.Content = new StringContent(val);
- var contenttype = (MediaTypeHeaderValue)WebApiCache.Get(_cachekey + ":responsect");
- if (contenttype == null)
- contenttype = new MediaTypeHeaderValue(_cachekey.Split(':')[1]);
- ac.Response.Content.Headers.ContentType = contenttype;
- ac.Response.Headers.CacheControl = setClientCache();
- return;
- }
- }
- }
- }
- else
- {
- throw new ArgumentNullException("actionContext");
- }
- }
- public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContextactionExecutedContext)
- {
- if (!(WebApiCache.Contains(_cachekey)))
- {
- var body = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
- WebApiCache.Add(_cachekey, body, DateTime.Now.AddSeconds(_timespan));
- WebApiCache.Add(_cachekey + ":response-ct",actionExecutedContext.Response.Content.Headers.ContentType, DateTime.Now.AddSeconds(_timespan));
- }
- if (_isCacheable(actionExecutedContext.ActionContext))
- actionExecutedContext.ActionContext.Response.Headers.CacheControl = setClientCache();
- }
- }
Step 9: Set action with attribute
We have finished the entire configuration, now we will set the attribute over the action. Here is the code of my Get() action within the Values controller.
- [WebApiOutputCache(120, 60, false)]
- public string Get()
- {
- return "Time is:-" + DateTime.Now.ToLongTimeString();
- }
Step 10: Output


Krishna ParabPosted May 13, 2020, 1:56 PM
Hi sir please can you share this code
Nizar NooraniPosted Mar 27, 2014, 10:44 AM
The use of anonymousOnly does not make much sense for APIs since we aren't dealing with static vs. dynamic page content. I think the behavior should change to allowAnonymous - cache for all users vs. authenticated users only. Thoughts?