OutputCache doesn’t work with Web API – Why? A solution

Output Cache is one of the most useful features of ASP.NET and plays a key role for making high performance web applications. It’s very easy to use this feature. Just put OutputCache attribute on any controller/action or you can set outputCacheSettings attribute in web.config. ASP.NET MVC and ASP.NET Web API are two technologies that looks very similar when we work with them. They have similar concepts like Controller, Action, routing etc and work almost in similar way except one returns View and returns data in JSON/XML format.

Recently in an application, I was required to cache some web api calls so I put the OutputCache attribute over the Web API action but when I ran it was not working. The data was not getting cached and on every request, it was going to server and further fetching data from database and returning the same. Then to verify the attribute, I put the same attribute over a MVC action and it was working like a charm. So I felt that it looks like that this attribute is not working.

It made me bit irritated and I was expecting that it should give an error if it is not supported then. I had an impression that all the features available with MVC also works with Web API and vice versa. Let’s have a look with an example

1- I have created a TestController (MVC) and Index View.

2- Then I am assigning current time in ViewBag and displaying in the view.

3- Then I decorated the Action with OutputCache attribute. Let’s see the code

    public class TestController : Controller
    {
        [OutputCache(Duration = 20, VaryByParam = "none", Location = OutputCacheLocation.ServerAndClient)]
        public ActionResult Index()
        {
            ViewBag.Message = "Message set at server at " + System.DateTime.Now.ToLongTimeString();
            return View();
        }
    }

We saw the TestController above. Let’s look at Index View

@{
    ViewBag.Title = "View";
}
<h2>My MVC View</h2>
@ViewBag.Message

It’s simple.Now lets run it

cachedmvcAs here we can see that the time is not getting updated after multiple refreshes. This is very expected behavior that we want from the attribute.

Now let’s See the Web Api part.
1- I created a TestDataController which inherits from ApiController.
2- It has a Get method which return a list of string. Here for simplicity, it contains two items- DateTime in normal and UTC format.
3- Here I also decorated with OutputCache attribute to the Web API method as I did in MVC example.
4- Then I created another MVC action (Details) in TestController and created a View accordingly.
5- Now from the Details view, I am calling the web api via jQuery Ajax.

Let’s quickly have a look at the code

public ActionResult Details()
{
    return View();
}

Web API Controller :

    public class TestDataController : ApiController
    {
        [OutputCache(Duration = 20, VaryByParam = "none", Location = OutputCacheLocation.ServerAndClient)]
        public IEnumerable<string> Get()
        {
            return new string[] { DateTime.Now.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString() };
        }
    }

Above controller has the same OutputCache attribute.

MVC View:

<h2>Details</h2>
<input type="button" value="Call Api" onclick="LoadData();"/>&nbsp;
<div id="dvapidata"></div>

<script src="~/Scripts/jquery-1.10.2.js"></script>
<script>
    LoadData();
    function LoadData() {
        $.ajax({
            type: "Get",
            url: "/api/TestData",
            contentType: "application/json; charset=utf-8",
            data: "{}",
            dataType: "json",
            success: AjaxSucceeded,
            error: AjaxFailed
        });

    }
    function AjaxSucceeded(result) {
        $('#dvapidata').html(result);
    }
    function AjaxFailed(result) {
        alert('no success');
    }
</script>

Now let’s run that and have a look

webapinocache

 

Oh.. here the data is not cached and time is getting updated in every call which is not expected.

Let’s analyze why it did not work for Web API

To get the details, I tried to dig it further to see details of the HTTP request and response for both (MVC and Web API). Let’s have a look that I collected via fiddler

ResponseHeadersSo clearly we can see in the left side (Red encircled area) that Cache header is set properly which is response header for MVC action, while in the right side, we see that (Blue encircled area), Cache control is set as no-cache. It means OutputCache attribute is not setting the proper cache header in response for web api.

To solve this we can created our own custom filter the adds the appropriate header at web api call as we saw above so that it can be cached at client and then decorate that attribute at Web  API call. Lets create that

    public class CacheWebApiAttribute : ActionFilterAttribute
    {
        public int Duration { get; set; }

        public override void OnActionExecuted(HttpActionExecutedContext filterContext)
        {
            filterContext.Response.Headers.CacheControl = new CacheControlHeaderValue()
            {
                MaxAge = TimeSpan.FromMinutes(Duration),
                MustRevalidate = true,
                Private = true
            };
        }
    }

In the above code, we have overridden OnActionExecuted method and set the required header in the response. Now I have decorated the Web API call as

  [CacheWebApi(Duration = 20)]
        public IEnumerable<string> Get()
        {
            return new string[] { DateTime.Now.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString() };
        }

Now let’s run the application.

CachedwebApiIt is working as expected and the response is getting cached. Lets see the response header via fiddler now

WebApiHere see the blue encircled area, cache header is set. So this is the reason response is getting cached now.

Hope you have enjoyed the post and it has helped you.

Cheers
Brij

11 thoughts on “OutputCache doesn’t work with Web API – Why? A solution

    • It is a kind of design issue with Web API. Initially MS provided support for REST with WCF but later they introduced Web API as a part of ASP.NET Stack and tried to make it similar with MVC. But could not make it completely similar as MVC as both required major design change that’s why Web API controller inherits ApiController. But they are changing the complete framework from bottom in ASP.NET 5 and there you wont find similar anomalies.

      Cheers
      Brij

    • Thanks for your feedback. I have seen that while doing research for this post. I think it would be good if I add in the post for our readers. Actually my whole idea for this post, was to highlight the dependency and the cause.

      Thanks
      Brij

  1. Pingback: A Great unification of Controllers: ASP.NET 5 | Code Wala

  2. Thanks for the useful article Brij. I was wondering what may be different between this and standard outputcaching and one important difference for anyone attempting to replicate Output Cache in the meantime is this:
    “By default, when you use the [OutputCache] attribute, content is cached in three locations: the web server, any proxy servers, and the web browser. ”
    This example above however will be only caching locally on the web browser (i.e. only improving performance for cases when same browser is requesting the same content, not directly improving performance for other web visitors who would benefit from a web server cache)

    • Your are right!! Here I have used the browser’s capability of Caching that was required for in my application. In Web APIs, I have seen use of Browser’s one mainly. It can be extended based on the need as well.

      Also, the lasted version of ASP.NET Core changed a lot. There MVC and Web API got unified and this feature works exactly same in both cases.

  3. Thanks Brij for this nice post, Just wondering if OutputCache not working with web API has something to do with Owin implementation by design whereas asp.net MVC 5 has no Owin implementation. Not sure about the new .net core 1.0 still experimenting

    • I also have to check in ASP.NET Core 1.0 but I am sure it will work as Controllers are same. I dont think it has any relation with OWIN in MVC 5. Earlier both the MVC and Web API were implemented differently. That was the key reason.

  4. thx for this post. however how can i check if there is a data in the cache , and if there is get the data from cache and not calling the webAPI.
    thx in advance

    • When you consume the service via Client side, it uses HTTP Cache control header for caching. The header should be set properly as I mentioned in the post. You don’t need to check anything. Browser itself checks the data in cache and if it get expired then calls to web api for data.

Leave a comment