Saturday, October 27, 2012

Donut Caching with ASP.NET MVC 4

I'm working on a new personal project during my free time and I chose to develop it with ASP.NET MVC 4 (hosted on Azure of course...) to test the new features of this version.

In this project, I need to display "Logged in as xxxx " (if the user is logged...) in the header of the page. This is a simple scenario to implement but caching could complicate the development of my web app...

My header is in my layout and is used everywhere but I would like to place an OutputCacheAttribute on some actions (the home page, etc.). If I put the attribute on an action, all of my users will see an header with "logged in as xxxx" of the first incoming query!

The solution to this problem is named "Donut Caching": you send a cached page from the server with a "fresh" portion which is not in cache (the donut's hole).
Concretely, the server does not generate the requested page (query the DB, etc.): it just needs to take the cached version and replace a specific part.

Luckily, you do not need to develop your own custom OutputCacheAttribute because there is already a very good package (There's An App A Pack For That... thanks NuGet!): MvcDonutCaching for ASP.NET MVC 3 and later...

To install the package, you can search through NuGet or directly type the following command in the "Package Manager Console":

   1:  install-package MvcDonutCaching


The use of this package is very simple... They have created several extension methods for HtmlHelper with the usual parameters plus another boolean parameter: excludeFromParentCache. I just have to call my partial view (with the "connected has xxxx"...) like that:


   1:  ...
   2:   
   3:  @Html.Action("LoginHeader", "Account", new { lang }, true)
   4:   
   5:  ...

The last parameter of Html.Action is set to true: this partial view will be excluded from the parent cache (if there is a cache on the action...). (do not pay attention to new { lang }, it's a route's value)

To make it work, you must not use the OutputCacheAttribute but the DonutOutputCacheAttribute:


   1:  [DonutOutputCache(Duration = 60, VaryByParam = "lang")]
   2:  public ActionResult Index(string lang)
   3:  {
   4:      ...
   5:   
   6:      return View();
   7:  }

And that's it! The first request will be cached during 60 seconds but the LoginHeader partial view will be generated every time.

(note: of course, the donut caching only works on the server side...)

3 comments:

  1. Hi I am just trying something similar and implemented a child action to render a partial view. My child action controller is like this:

    public class MenuController : Controller
    {
    [ChildActionOnly]
    public ActionResult DynamicLogin()
    {
    return View();
    }

    }

    I am calling this action in the shared layout _Layout.cshtml like

    @Html.Action("DynamicLogin", "Menu", true)

    When I run the application the DynamicLogin action goes into infinite recursive call and I am unable to figure out what is causing this. I am just curious to find out how you managed to avoid the same problem or how is your solution different than mines? Could you please share your child controller and view code as well?
    Thanks

    ReplyDelete
    Replies
    1. Solved this! Solution is to return a PartialView instead of full View.
      Cheers :)

      Delete
    2. Glad to know that your problem has been solved!

      Delete