Donut Caching in ASP.NET MVC

UPDATE: Due to differences in the way that ASP.NET MVC 2 processes request, data within the substitution block can be cached when it shouldn’t be. Substitution caching for ASP.NET MVC is not supported and has been removed from our ASP.NET MVC Futures project.

This technique is NOT RECOMMENDED for ASP.NET MVC 2.

With ASP.NET MVC, you can easily cache the output of an action by using the OutputCacheAttribute like so.

[OutputCache(Duration=60, VaryByParam="None")]
public ActionResult CacheDemo() {
  return View();
}

One of the problems with this approach is that it is an all or nothing approach. What if you want a section of the view to not be cached?

mmmm-doughnut Well ASP.NET does include a <asp:Substitution …/> control which allows you to specify a method in your Page class that gets called every time the page is requested. ScottGu wrote about this way back when in his post on Donut Caching.

However, this doesn’t seem very MVC-ish, as pointed out by Maarten Balliauw in this post in which he implements his own means for adding output cache substitution.

However, it turns out that the Substitution control I mentioned earlier makes use of an existing API that’s already publicly available in ASP.NET. The HttpResponse class has a WriteSubstitution method which accepts an HttpResponseSubstitutionCallback delegate. The method you supply is given an HttpContext instance and allows you to return a string which is displayed in place of the substitution point.

I thought it’d be interesting to create an Html helper which makes use of this API, but supplies an HttpContextBase instead of an HttpContext. Here’s the source code for the helper and delegate.

public delegate string MvcCacheCallback(HttpContextBase context);

public static object Substitute(this HtmlHelper html, MvcCacheCallback cb) {
    html.ViewContext.HttpContext.Response.WriteSubstitution(
        c => HttpUtility.HtmlEncode(
            cb(new HttpContextWrapper(c))
        ));
    return null;
}

The reason this method returns a null object is to make the usage of it seem natural. Let me show you the usage and you’ll see what I mean. Referring back to the controller code at the beginning of this post, imagine you have the following markup in your view.

<!-- this is cached -->
<%= DateTime.Now %>

<!-- and this is not -->
<%= Html.Substitute(c => DateTime.Now.ToString()) %>

On the first request to this action, the entire page is rendered dynamically and you’ll see both dates match. But when you refresh, only the lambda expression is called and is used to replace that portion of the cached view.

I have to thank Dmitry, our PUM and the first developer on ASP.NET way back in the day who pointed out this little known API method to me. :)

We will be looking into hopefully including this in v1 of ASP.NET MVC, but I make no guarantees.

What others have said

Requesting Gravatar... David Pellerin Nov 05, 2008 4:50 PM
# re: Donut Caching in ASP.NET MVC
very interesting. looking forward to trying this out.
Requesting Gravatar... Andrei Rinea Nov 05, 2008 6:40 PM
# re: Donut Caching in ASP.NET MVC
This definitely must make it into V1. Definitely.
Requesting Gravatar... Jason Nov 05, 2008 6:56 PM
# re: Donut Caching in ASP.NET MVC
Yeah, get this in there. We'll wait a few more days/weeks. :)
Requesting Gravatar... PK Nov 05, 2008 7:07 PM
# re: Donut Caching in ASP.NET MVC
+1111111111111111111111 to getting this into v1 pwitty please! Awesome post. Donut caching is one of those wikidly kewl secret gems in ASP.NET.

Cheers Phil!
Requesting Gravatar... dj-nitehawk Nov 05, 2008 7:55 PM
# re: Donut Caching in ASP.NET MVC
yesss please please pleeeease include this in v1.

and dont forget to include the ability to define custom validation messages at the model level. this is the only thing im missing at the moment with the beta.

keep up the good work!
Requesting Gravatar... Jason Whitehorn Nov 05, 2008 8:18 PM
# re: Donut Caching in ASP.NET MVC
Yum, tasty. Especially if you guys can get this in v1.

This would actually come in very useful for several things I am currently working on. So I am really hoping you guys can pull this off.
Requesting Gravatar... Shiju Varghese Nov 05, 2008 10:59 PM
# re: Donut Caching in ASP.NET MVC
Phil,
please include this in v1.
Requesting Gravatar... Ken Egozi Nov 06, 2008 1:36 AM
# re: Donut Caching in ASP.NET MVC
I still can't see the merit in the whole Partial caching thing.

Server side caching should be about putting results of expensive logic in a cache. Views are supposed to be just a bunch of Write() calls into the response stream. this writing-to-the-stream thing will still happen even if the rendered view is cached, as it's needed to be, er, written.

I think that there's no performance gain here. If anything, the memory overhead will only cause more damage than good.

and if one finds out that view renderings start to get expensive, then perhaps there's too much responsibility in the wrong place
Requesting Gravatar... Gergely Orosz Nov 06, 2008 3:08 AM
# re: Donut Caching in ASP.NET MVC
@Ken: I agree with most of what you say. Just one addon: I often see developers not separating their BL logic and mixing it with the code (take inline coding as the best example). I feel that this approach is yet another small hack which can be helpful in a _lot_ of scenarios but I would prefer designing the program in a way that there's no need in using this feature.
Requesting Gravatar... tasarım Nov 06, 2008 3:45 AM
# re: Donut Caching in ASP.NET MVC
This is really a cool hack. I hope it makes in v1 of MVC, cause it could be very useful for some things i am working on.
Requesting Gravatar... Dan Atkinson Nov 06, 2008 3:59 AM
# re: Donut Caching in ASP.NET MVC
This would be extremely useful, if only sub-controllers were part of the core (not just community projects like MVCContrib), which would then allow me to cache multiple actions rendered inside the master page.
Requesting Gravatar... Scott Watermasysk Nov 06, 2008 5:50 AM
# re: Donut Caching in ASP.NET MVC
I am with Ken and Gergely on this one. Just because you can do it, doesn't mean you should.

Output caching is one of those things that demo's really well and completely falls apart when you really need.

In addition to mixing your BL into your views you also in many cases now need to work with that logic in your back end so that you can clear the cache something changes some where else in the site.

-Scott
Requesting Gravatar... Chris Nov 06, 2008 9:35 AM
# re: Donut Caching in ASP.NET MVC
+1 in v1

just wanted to note, our main subdomain, is totally an mvc app.
Requesting Gravatar... Kyorry Wang Nov 07, 2008 1:17 AM
# re: Donut Caching in ASP.NET MVC
hi, Phil
how about to present ViewUserControl?

i test the Html.RenderParial(partialName), to render user control that i had set the cache like this <%@ OutputCache Duration="60" VaryByParam="none" %>, but it does not work, why?
Requesting Gravatar... Torkel Ödegaard Nov 07, 2008 4:24 AM
# re: Donut Caching in ASP.NET MVC
I am also missing how this solves the real problem. This only caches the view generation, which is in most cases pretty cheap, what one wants to cache is part of the view & including the controller action logic that is responsible for the viewdata for that part of the view.

I think the real solution for donate caching in an MVC app is subcontrollers and actions, where the complete action and rendering of that sub-action can be cached.
Requesting Gravatar... brad dunbar Nov 07, 2008 7:12 AM
# re: Donut Caching in ASP.NET MVC
While I can't comment on whether this solves the issue, I will say this is a great response to real world feedback. I've heard the stackoverflow team talk about this constantly and I think its amazing that you're taking steps to fix it so quickly.

This is a glimpse of a new, transparent Microsoft that I would like to see more of. Great work Phil, keep it up.
Requesting Gravatar... Torkel Nov 07, 2008 10:17 AM
# re: Donut Caching in ASP.NET MVC
Maybe to clarify, I think that this solution can be very useful and it would be great if it makes it into v1. Great work Phil :) But as I said it only caches the view generation, which in a WebForms app makes sense because the view generation and logic & data fetches is intermingled, but in a mvc app it makes less sense.
Requesting Gravatar... Jon Davis Nov 12, 2008 2:03 PM
# re: Donut Caching in ASP.NET MVC
Ironically, I think I could lose wait by eating these donuts...
Requesting Gravatar... DanielChow Nov 13, 2008 10:14 PM
# re: Donut Caching in ASP.NET MVC
Great job,Phil! It's Every useful!
Requesting Gravatar... labilbe Nov 14, 2008 8:23 AM
# re: Donut Caching in ASP.NET MVC
How to substitute something like an Html.ActionLink?

Maybe something like should be helpful

using (Html.Substiture()) {
Html.ActionLink...;
}
Requesting Gravatar... roni Dec 01, 2008 12:14 AM
# re: Donut Caching in ASP.NET MVC
why not simple take out the cache from your application and put it into an external application like my sharedcache? Finally MS understood in version 4.0 and velocity project - caching should not be within the same app.
Requesting Gravatar... BorisCallens Dec 24, 2008 1:14 AM
# re: Donut Caching in ASP.NET MVC
I must be missing something.
I have an action method with the same [OutputCache(Duration = 60, VaryByParam = "None")] attribute.
Then in my master there is a "Welcome[Username]" string that obviously should vary if a new user logged in, so I put <%= Html.Substitute(c => String.Format(ViewData.Model.T9nProvider.TranslateById("CommonWeb.Welcome"), Context.User.Identity.Name)) %>
And then put a breakpoint there.
First time everything runs smoothly. Second time round, it never even hits the method (because it is fetched from cache I presume.
What am I missing?
Requesting Gravatar... gene Mar 25, 2009 7:18 PM
# re: Donut Caching in ASP.NET MVC
Did this make it into 1.0?
Requesting Gravatar... Harry Mar 31, 2009 9:01 PM
# re: Donut Caching in ASP.NET MVC
Nope! this did not make it into version 1 :(
Requesting Gravatar... Ann Onymouse Dec 26, 2009 2:46 AM
# re: Donut Caching in ASP.NET MVC
This did not make it into version 2 either: "Due to changes in the rendering behavior of MVC view engines, the Html.Substitute helper method does not work and has been removed" (from Futures).

What's up? Is Donut Caching now officially a don't-go-there in MVC?
Requesting Gravatar... headache Jan 20, 2010 7:17 AM
# re: Donut Caching in ASP.NET MVC
How would you substitute an partial view ?
Most of the times obtaining that Html is not that easy as calling DateTime.Now so it would be nice to do something like

<%= Html.SubstituteAction("SomeAction", "SomeController") %>

Right now there is no easy/fun way to render a view on the controller:
stackoverflow.com/.../render-a-view-as-a-string
Requesting Gravatar... Richard Feb 05, 2010 10:50 AM
# re: Donut Caching in ASP.NET MVC
This doesn't work with ASP.NET MVC 2 RC 2, does it?
Requesting Gravatar... Eric Mar 05, 2010 9:02 AM
# re: Donut Caching in ASP.NET MVC
UNFORTUNATELY, this only works for strings. You can't get this to work if you are using RenderAction or RenderPartial because they return void.

Silly that nobody has come up with an easier way to exclude these methods from cache.
Requesting Gravatar... headache Mar 22, 2010 6:15 AM
# re: Donut Caching in ASP.NET MVC
Now (in version 2.0) we have Html.Action so we can obtain the Html. Yeeii
But Response.WriteSubstitution doesn't work anymore. Aahhhhh
Requesting Gravatar... Pascal Lacroix Apr 28, 2010 1:31 AM
# re: Donut Caching in ASP.NET MVC
Dear Phil,

Is there any way to get this working with ASP.NET MVC 2?
Some ideas?
Requesting Gravatar... Fabio Milheiro May 03, 2010 9:18 AM
# re: Donut Caching in ASP.NET MVC
Yes, I have been all day try to get this to work with ASP.NET MVC 2!!! HELP!
Requesting Gravatar... Chicago mover Aug 12, 2010 2:45 AM
# re: Donut Caching in ASP.NET MVC
I too have the same problem and is there any solution to get this working with ASP.NET MVC 2?

What do you have to say?

(will show your gravatar)
Please add 6 and 2 and type the answer here: