When it comes to performance few techniques are as effective as full page caching. When one can completely eliminate external databases from the equation performance often becomes simply a question of how fast one’s Internet connection is. Unfortunately there are usually a couple bits of information, sometimes just the user’s name, which precludes the use of a full page cache. This is where techniques known as partial caching come into play.
Partial caching is often divided into two categories, “donut caching” and “donut hole caching”. We’ll illustrate the difference using ASP.NET Web Forms.
With donut hole caching one is caching bits and pieces of a website. In Web Forms it requires refactoring the page so that reusable content is physically separated from user-specific content via controls. Once that is done one can use caching directives on the individual controls. Unfortunately this greatly restricts the way the designer lays out the page.
With donut caching the entire page is cached except for specific “holes” that will be filled in later. In Web Forms 2.0 and later these holes are known as substitution blocks. A substitution block is implemented as special control that is excluded from the otherwise fully cached page. A static callback is then used to fill out the block at runtime. Scott Guthrie offers an example:
Shared Function LoginText(ByVal Context As HttpContext) As String Return "Hello " & Context.User.Identity.Name End Function
Until fairly recently ASP.NET MVC didn’t support any form of partial caching. The best one could do is include a AJAX call that would request some cached JSON or HTML data to be reassembled in the browser. But starting with MVC 3, released back in January, one can cache the result of child actions called from the parent view. Invoked using Html.Action or Html.RenderAction, these resemble control-level caching from Web Forms. There are some limitations for partial view caching, summarized by Paul Hiles as:
- Cache Profiles are not supported
- Disabling caching in the web.config is ignored
- It is not easy to remove output cache items, particularly for child actions
- Using a custom output cache store is problematic
Just as troubling is the complete lack of support for donut caching and substitution blocks. Since it isn’t currently on the roadmap for ASP.NET MVC 4, Paul is offering the open source project MvcDonutCaching. This extension is based on an ActionFilterAttribute that will “serialize the action arguments and return an html comment containing the arguments”.
This isn’t a perfect solution, at runtime it has to parse the cached output looking for the aforementioned HTML comments using a regular expression. Once found the associated Html.Action is invoked and the comment replaced with the user-specific content.
MvcDonutCaching actually appears to be easier to use than Web Forms substitution blocks. Presumably one is already using child actions for user-specific content, so turning on partial caching can be as simple as tagging the main view and switching to a new overload for Html.Action that includes an excludeFromParentCache attribute.