Caching infrastructure in MVC4 with C#: caching controller actions

You can introduce caching in many places within your web application: you can cache service responses, repository responses or specific database call results. In this post we’ll look at how to cache controller action results. Actions in MVC generally return Views of some sort. These views can be cached on the controller level so that the code within the action body doesn’t need to execute with every call.

Create the test webapp

To demonstrate the caching techniques we’ll build a very simple MVC4 web application. We’ll pretend that extracting the contact data of our company requires some long running database call. Create an MVC4 web application with .NET4.5 as the underlying framework. Navigate to HomeController.cs and locate the Contact action. It should look as follows:

public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }

Insert the following code just before the return statement:

Thread.Sleep(2000);

So we pretend that it takes 2 seconds to retrieve the contact information. Set a break point at ViewBag.Message and run the application. Navigate to the Contact action by clicking the ‘Contact’ link in the upper right hand corner. You’ll see of course that the execution breaks at the break point.

The OutputCache attribute

Normally the company’s contact data is pretty static so it’s futile to let the same 2 second logic run every time the Contact data is requested. The easiest way to save the ActionResult of the Contact action in the cache is to use the OutputCache attribute. The minimum requirement here is to specify the duration in seconds how long the action result should be cached. Update the Contact action as follows:

[OutputCache(Duration=10)]
public ActionResult Contact()

Run the application again and navigate to the Contact page. The first time you load this page the execution will break at the break point. Let the Contact page load normally. Now click the Contact link again; you’ll notice that the code execution did not break at the break point you inserted. As the action result is kept in the cache for 10 seconds the same View is returned on subsequent requests without running the code within the action method. Now wait at least 10 seconds and click the Contact link; as the Contact action result has been erased from the cache the code execution will break at the break point.

This was nice and easy and is a great solution for pages where the view changes very little over time.

OutputCache with parameters

If the action has parameters such as a search term then it’s of course not a good idea to simply cache the output as above. The user will see the same View result regardless of the search term. It would be better to cache the result of the search instead. This is not difficult to achieve either:

[OutputCache(Duration=3600, VaryByParam="searchTerm")]
public ActionResult Contact(string searchTerm)

The VaryByParam allows you to specify the parameters by which the result should be cached. Here we specify that the View result should be cached for each value of the searchTerm parameter. You can specify to cache the output for every thinkable combination of the parameters by writing VaryByParam=”*”. This is probably the best option and is also the default one. If for whatever reason you want to serve the same View regardless of the parameter values then you can specify VaryByParam=”none”.

Run the application and navigate to the contact page. The execution will break as expected. Next update the URL as follows:

http://localhost:xxxx/Home/Contact?searchTerm=a

…and press Enter. The execution will break again as the parameter is different; it was null the first time. Next update the query string to

?searchTerm=b

The execution will break as expected. Now try ?searchTerm=a again. The execution should not break as the View result has been put into the cache for searchTerm = “a”. Change back to ?searchTerm=b and the cached result will be returned.

Cache child action results

You can specify different caching policies for partial views within the parent view. Add the following action to HomeController.cs:

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

Right-click inside the action body and choose the Add View… option. In the Add View window accept all default values but check the ‘Create as a partial view’ checkbox. The PartialViewTest.cshtml file will be created. Add the following content in that file:

<p>
    This is a partial view.
</p>

Open Contact.cshtml and add the following code just underneath the closing hgroup tag on the top of the page:

@Html.Action("PartialViewTest")

The Action method will execute the action called PartialViewTest within the HomeController and return its partial view.

Navigate to the Contact page and you should see the contents of the partial view as follows:

Partial view result

Now you can specify different caching policies for the Contact and PartialViewTest actions. Change the output cache attribute of the Contact action to the following:

[OutputCache(Duration=3)]

We only specify a short caching period so that we don’t sit and wait for the cache to be erased.

Decorate the PartialViewTest action as follows:

[OutputCache(Duration=3600)]

The effect should be that although the Contact action body executes after 3 seconds, the child view should be cached. Insert a break point within both action bodies and run the application. As usual, navigate to the Contact page. You should see that execution breaks within both method bodies. Now wait at least 3 seconds and refresh the page. Execution should now only stop within the Contact method but leave the PartialViewTest method alone as the partial view of that action is still in the cache.

So, you can independently set the cache strategies for controller actions and their child actions.

Note however, that you may only specify the Duration, VaryByCustom, and VaryByParam cache parameters for child actions. If you try to e.g. define the VaryByHeader parameter you’ll receive an exception.

Specify the cache location

You can cache the action results on the client, the server or both using the Location parameter as follows:

[OutputCache(Duration = 3600, Location=System.Web.UI.OutputCacheLocation.ServerAndClient)]

The default value is ServerAndClient.

Cache according to the header values

If you have a multi-culture website where the user can specify the language of the web site then it’s a good idea to cache according to the available language. You don’t want to return a German-language view if the user is requesting the page in French. The language can be specified in the HTTP header called ‘Accept-Language’. So if this can vary then you can specify the following caching strategy:

[OutputCache(Duration = 3600, VaryByHeader="Accept-Language")]

Another type of header value will be important for AJAX requests. Let’s imagine that the Contact action is also available to AJAX calls. In other words only that portion of the screen will be refreshed which makes up the View result of the Contact action. The rest, i.e. the header, footer, menu etc. will stay constant. If a user then bookmarks a specific search result, e.g.:

http://localhost:xxxx/Home/Contact?searchTerm=a

…and will call this search directly from his/her browser then the Contact action will only serve up a cached version of the Contact view and nothing else. The reason is that user’s browser will send a full GET request and not an asynchronous one. All the other parts of the ‘normal’ full screen, i.e. everything that the AJAX call didn’t touch will be left out. The result will be very weird: a page devoid of styles – apart from any inline style tags in the Contact view – headers, footers etc. The problem lies with a specific header value: X-Requested-With. This is the only difference between the headers of the AJAX and the GET calls: it will be present on an AJAX request but absent on GET requests. To the caching engine there’s no difference between AJAX calls and GET calls because you didn’t specify this in the OutputCache attribute. Two things need to be done:

1. Update the OutputCache attribute to vary the output by this specific header type:

[OutputCache(Duration = 3600, VaryByHeader="X-Requested-With")]

Now the caching mechanism will be able to differentiate between two requests: one that wants a full page render and one that will only serve a portion of the page.

Unfortunately there are cases when this update is not enough. The default setting is that we’re letting the action result be cached on both the client and the server. However, some older browsers are not “clever” enough to detect the difference between full GET and partial AJAX requests.

2. To be on the safe side we need to specify that caching only should occur on the server:

You can achieve this as follows:

[OutputCache(Duration = 3600, VaryByHeader="X-Requested-With", Location=System.Web.UI.OutputCacheLocation.Server)]

Now the server will send instructions to the browser not to cache the action result. The browser will always have to access the server for a response. The server will then serve up a cached result if there’s any in its memory.

The safest and most elegant solution is of course to create separate action methods for full GET and partial AJAX calls.

Sql dependency caching

There is a parameter to the OutputCache attribute called SqlDependency. You can define so that if the contents of an SQL table change then the cache should be refreshed.

It sounds very tantalising but this solution is not widely used. There are significant restrictions on the type of SQL query that can be used.

Custom caching

If all else fails then you can build your own caching algorithm and specify this algorithm in the VaryByCustom parameter. You can find an example on MSDN here.

Cache profiles

It is generally bad practice to use hard coded strategies to tweak your solution. The same is valid for the OutputCache attribute: if you want to update the caching policy in your OutputCache attributes then you have to change each one of them one by one and redeploy your solution. This makes testing out the correct level of caching quite cumbersome.

Instead you can specify caching profiles in the web.config files and refer to these profiles by name in the OutputCache attribute.

You can specify your caching strategies within the system.web tag as follows:

    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="Aggressive" duration="1600"/>
          <add name="Short" duration="20"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>

…and refer to them in the OutputCache attribute like this:

[OutputCache(CacheProfile="Aggressive")]

This way you can simply change the values in web.config and see the effects immediately – there’s no need to redeploy your application.

View the list of MVC and Web API related posts here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

9 Responses to Caching infrastructure in MVC4 with C#: caching controller actions

  1. Pingback: Training : Microsoft ASP.NET MVC | Stephen Haunts { Coding in the Trenches }

  2. Erhan says:

    VaryByParam does not enough for PartialViewTest to refresh itself with different param.

    [OutputCache(Duration=10, VaryByParam=”searchTerm”)]
    public ActionResult PartialViewTest{…}

    it’s not working, is it something wrong with mvc 4 or am i wrong..:)

  3. Alexander says:

    Thank you Andras Nemes. This was helpful for me :)!

  4. Niklas Arbin says:

    Important note is that this works for one level of Child Actions, more levels of caching will result in an InvalidOperationException

  5. Jeetu says:

    @Andras : If I want to use cached data as collection then can i use below code? or what sould I do?
    [OutputCache(Duration=10, VaryByParam=”searchTerm”)]
    public ActionResult Test(string searchTerm){….}

    • Andras Nemes says:

      Jeetu, what do you mean by “cached data as collection”? Can you give an example of such a collection and how you’re intending to use it?
      //Andras

  6. Jogi says:

    @Andra
    I would like to use Cache on Business layer so is it profession al way for me to do:
    [OutputCache]
    or suggest anything else .
    I am using ASP.NET C# MVC

  7. John Moreno says:

    This doesn’t seem to work with the webapi (controller inherits from ApiController). See the answer to: http://stackoverflow.com/questions/11547618/output-caching-for-an-apicontroller-mvc4-web-api

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: