I've had a fun morning trying to cache partial views inside a dynamic page with MVC 3. Specifically, a partial view in a razor layout page (i.e. masterpage) that shows some dynamically generated data which I would like to hold off from regenerating for 60 seconds or so, even if the user refreshes or navigates around the site.
So I create my partial view like this - here we generate the time to show when the view is being cached
MyPartial.cshtml
Timestamp: @DateTime.Now.ToLongTimeString()
Then I create the controller action:
HomeController.cs
[OutputCache(Duration = 60)]
public ActionResult MyPartial()
{
return PartialView();
}
Then I amend my layout page to include the call to the PartialView:
MyMaster.cshtml
Some header stuff
...
@Html.Action("MyPartial", "Home")
...
Some footer stuff
Well, that's all there is to it. The pitfalls I faced revolved around accidentally calling
Html.Partial() from the layout page, which didn't work because it bypasses the controller attributes (i.e. it misses the
OutputCache directive).
HOWEVER
Then I started wanting to reset the output cache when the underlying data changed, or when some user setting changed. So I knocked up this little solution using the
OutputCache attribute's
VaryByCustom property.
First a modification to the controller:
HomeController.cs
[OutputCache(Duration = 60, VaryByCustom = "WebApp.OutputCacheKey")]
public ActionResult MyPartial()
{
return PartialView();
}
Every time OutputCache is called, it will check to see if that string has changed since last time. If it has changed, it invalidates the cache. "But it's a static string! How can it change?" I hear you cry. Yup, here's some voodoo magic. You can override a special method in global.asax that intercepts the call and returns ANOTHER string, wooo!
global.asax
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg == "WebApp.OutputCacheKey")
{
return WebApp.OutputCacheKey;
}
else
{
return base.GetVaryByCustomString(context, arg);
}
}
Now, we need to implement this
WebApp thing. It's just an approach I like to have a static class knocking around my web application that allows me to access stuff from one central place, like strongly-typed accessors for session variables, or my per-request EF datacontext. But for these purposes it would just look like this:
WebApp.cs
public static class WebApp
{
internal static string OutputCacheKey
{
get
{
if (HttpContext.Current.Session["WebApp.OutputCacheKey"] == null)
{
ResetOutputCache();
}
return HttpContext.Current.Session["WebApp.OutputCacheKey"].ToString();
}
}
internal static void ResetOutputCache()
{
HttpContext.Current.Session["WebApp.OutputCacheKey"] = Guid.NewGuid().ToString();
}
}
So, what we have now is an easy way to programatically tell the OutputCache to clear itself, with a simple call from anywhere in your controller code:
WebApp.ResetOutputCache();
Simples!