.NET Zone is brought to you in partnership with:

Juri Strumpflohner mainly operates in the web sector developing rich applications with HTML5 and JavaScript. Beside having a Java background and developing Android applications he currently works as a software architect mainly dealing with the .Net technology stack. When he’s not coding or blogging about his newest discoveries he is practicing Yoseikan Budo where he owns a 2nd DAN. Juri is a DZone MVB and is not an employee of DZone and has posted 48 posts at DZone. You can read more from them at their website. View Full User Profile

Output Caching in ASP.NET MVC

10.29.2012
| 11453 views |
  • submit to reddit

Although nowadays most of us have broadband connections, resource caching is important as loading a resource from your local HD is (by now) still faster than fetching it remotely. In this post I’d like to explore how to control the ASP.net MVC caching behavior and its effects when using ajax requests for retrieving data.

Default ASP.net MVC Caching Behavior

If you don’t specify anything at all and you have a plain normal action method like

public JsonResult Details(long id)
{
    //snip snip
    return Json(theResult, JsonRequestBehavior.AllowGet);
}

then ASP.net MVC will return the response with the following headers:

Cache-Control:private
Connection:Close
Content-Length:81836
Content-Type:application/json; charset=utf-8
Date:Mon, 29 Oct 2012 08:08:44 GMT
Server:ASP.NET Development Server/11.0.0.0
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:3.0

According to the offical W3.org docs, Cache-Control:private …

…Indicates that all or part of the response message is intended for a single user and MUST NOT be cached by a shared cache. This allows an origin server to state that the specified parts of the response are intended for only one user and are not a valid response for requests by other users. A private (non-shared) cache MAY cache the response.

As a result, the browser might cache such ajax request made by a JavaScript client, which most of the time might not be desired. Especially our beloved IE used a massive caching approach leading to strange effects.

Disabling Caching Behavior

Lets first take a look on how to completely disable caching.

Globally on the Server (Custom Approach)

If we want to completely disable any kind of caching behavior, we could employ a kind of brute force approach by implementing a custom global action filter that sets the corresponding headers:

public class MvcApplication : System.Web.HttpApplication
{
    ...
     public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new NoCacheGlobalActionFilter());
    }
    ...
}

public class NoCacheGlobalActionFilter : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        ...
        HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
        cache.SetCacheability(HttpCacheability.NoCache);
        
        base.OnResultExecuted(filterContext);
    }
}

The response headers will vary as follows:

Cache-Control:no-cache
Connection:Close
Content-Length:81836
Content-Type:application/json; charset=utf-8
Date:Mon, 29 Oct 2012 08:48:40 GMT
Expires:-1
Pragma:no-cache
Server:ASP.NET Development Server/11.0.0.0
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:3.0

Note the Cache-Control and Expires header that has been added. This will prevent any kind of caching, on the server as well as on the client side.

Globally on the Server (OutputCache)

But rather than creating a custom filter, why not just re-use something existing. ASP.net has already a build-in caching mechanism called OutputCache. It is quite powerful and I’ll go into more detail soon. As a result you could annotate your controller as follows

[OutputCache(Duration = 0)]
public class SomeController : Controller  {

}

in order to prevent caching. Consequently the response headers contain a Cache-Control: public, max-age=0 header.

Attention, you could also specify the following in your web.config:

<caching>
    <outputCache enableOutputCache="false" />
</caching>

However, this won’t prevent caching. Instead it just indicates that no kind of caching mechanism should be applied. By just disabling the output cache we get the default cache headers used by ASP.net MVC which falls back to Cache-Control: private, thus again opening the browser the possibility to cache requests.

On the Client-Side using jQuery

Beside disabling the cache on the server-side, you also have the possibility to control the caching behavior on the client-side. jQuery’s ajax - for instance - allows you to specify a cache flag. Lets look at its effects. Executing an ajax request with

$.ajax({
    type: 'GET',
    url: '/nation',
    ...
});

uses the following headers:

GET /nation HTTP/1.1
Host: localhost:4120
Connection: keep-alive
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4
Referer: http://localhost:4120/frontend/accounts/index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en,de;q=0.8,en-US;q=0.6,it;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

and answers with

HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Mon, 29 Oct 2012 08:54:35 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 81836
Connection: Close

Instead, by using setting cache: false like

$.ajax({
    type: 'GET',
    cache: false,
    url: '/nation',
    ...
});

the request headers look as follows

GET /nation?_=1351500913222 HTTP/1.1
Host: localhost:4120
Connection: keep-alive
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4
Referer: http://localhost:4120/frontend/accounts/index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en,de;q=0.8,en-US;q=0.6,it;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

The response parameters don’t vary. But note that jQuery adds an additional, random generated request parameter _=... which is used to enforce a cache invalidation.

Enable Caching

Before I illustrated how to disable caching behavior. Now I’d like to take a look on how to enable it.

On the Server Side

While you could easily write your custom caching attribute using action filters, there is no need at all. ASP.net has the OutputCache mechanism which also provides a OutputCacheAttribute that can be applied to Controller actions. For instance we could decorate our controller action like

[OutputCache(Duration=3, VaryByParam="*")]
public JsonResult Details(long id)
{
    //snip snip
    return Json(theResult, JsonRequestBehavior.AllowGet);
}

This causes the following response headers to be injected:

HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Mon, 29 Oct 2012 09:18:35 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: public, max-age=3
Expires: Mon, 29 Oct 2012 09:18:37 GMT
Last-Modified: Mon, 29 Oct 2012 09:18:34 GMT
Vary: *
Content-Type: application/json; charset=utf-8
Content-Length: 81836
Connection: Close

Those of interest here are Cache-Control, Expires and Last-Modified.

Client Side Caching

We could potentially also enforce client-side caching mechanisms. I’m thinking about using techniques that have been made available with HTML5 like

To what regards the localStorage approach, there exists a nice jQuery plugin on GitHub which might be worth looking at: jQuery-ajax-jstorage-cache (with a fork by Paul Irish as well).

Conclusion: Caching Where and When I need It

In a real world application you’d obviously expect to have a proper combination of disabling/enabling the cache where appropriate. This implies to have the possibility to use an approach where you define a global default which can be overwritten where needed.

The approach I applied is to enforce disabling of the cache on a global basis for then enabling it on an as-needed basis. That is, all our controllers inherit from a custom BaseController class which performs some additional common work. I then annotated that class as follows

[OutputCache(Duration=0, VaryByParam="*")]
public class BaseController : Controller
{ ... }

On those action methods where I explicitly desire caching behavior I can now add the OutputCache attribute to enforce it:

public class SomeController : BaseController
{
    public JsonResult UnCachedAction(){ ... }

    [OutputCache(Duration=60, VaryByParam="id")]
    public JsonResult CachedAction(long id){ ... }
}

This turns out to be quite powerful as the amount of custom code involved is kept at a minimum. If you’re interested in using the OutputCache mechanism I’d suggest you to also take a look at custom Cache Profiles.

Published at DZone with permission of Juri Strumpflohner, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Vikas Wing replied on Tue, 2012/10/30 - 4:25am

Hello,

Thanks for providing such valuable information on caching concepts in MVC. It will help a lot to my and my collegues. A lot of basics are covered here. Here can you give more information on performance impact due to this caching on the application?

I will look forward to listen from you on this. Once again thanks.

Ilias Pazas replied on Thu, 2013/03/21 - 5:57am

Hello. After reading your article I must admit that I like the concept but there is a question. You have clearly stated that disabling global cache by using [OutputCache(Duration=0, VaryByParam="*")] in conjunction with <outputCache enableOutputCache="false" /> in web.config does NOT disable cache since it allows private caching to be used. After that, your suggested approach seems to have a gap. Am I correct or do I miss something?

Please respond because I am willing to adopt your approach.

Thanks, Ilias

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.