Asp.Net MVC 3 Partial Page Output Caching Not Honoring Config Settings
I have a simple partial view that I'm rendering in my main view with:
@Html.Action("All", "Template")
On my controller I have this:
[OutputCache(CacheProfile = "Templates")]
public ActionResult All()
{
return Content("This stinks.");
}
And in my config this:
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<clear/>
<add name="Templates" duration="3600" varyByParam="none"/>
</outputCacheProfiles>
</outputCacheSettings>
<outputCache enableOutputCache="false" enableFragmentCache="false" />
</caching>
This will fail at runtime with exception:
Error executing child request for hand开发者_运维知识库ler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper
And inner exception:
Duration must be a positive number
Now obviously it's not picking up my web.config settings, because if I change it to:
[OutputCache(Duration = 3600)]
It will work, but also notice in my web.config I turned off enableOutputCache and enableFragmentCache, but it doesn't honor these settings.
Curiously, in a normal view these settings work fine, so what is it about partial views that is breaking this? Am I missing something? The Gu says this should work just fine... In short, is it supposed to honor caching settings in web.config and if not, why not?
So I took a minute and looked at the MVC 3 source. The first thing that came to me was this feature seemed a bit hacky. Mainly because they are reusing an attribute that works in one situation honoring all of the properties and config settings, and then in the child action scenario just ignoring all of those settings and only allowing VaryByParam and Duration.
How one would go about figuring out what is supported is beyond me. Because the exception they want to throw that says Unsupported Setting will never get thrown unless you supplied a duration and a VaryByParam value
Here is the main piece of code that smells:
if (Duration <= 0) {
throw new InvalidOperationException(MvcResources.OutputCacheAttribute_InvalidDuration);
}
if (String.IsNullOrWhiteSpace(VaryByParam)) {
throw new InvalidOperationException(MvcResources.OutputCacheAttribute_InvalidVaryByParam);
}
if (!String.IsNullOrWhiteSpace(CacheProfile) ||
!String.IsNullOrWhiteSpace(SqlDependency) ||
!String.IsNullOrWhiteSpace(VaryByContentEncoding) ||
!String.IsNullOrWhiteSpace(VaryByHeader) ||
_locationWasSet || _noStoreWasSet) {
throw new InvalidOperationException(MvcResources.OutputCacheAttribute_ChildAction_UnsupportedSetting);
}
I'm not sure why this isn't called out in documentation, but even if it was the api should make it clear, or at least throw the right exception.
In short, partial output caching works, BUTT not like you would want it too. I'll work on fixing the code and honoring some of the settings like enabled.
Update: I fixed the current implemenation to at least work for my situation with respecting the enabled flag and allowing cache profiles from the web.config. Detailed in my blog post.
Here's a simpler approach if :
- Your basic goal is to be able to disable cache during debugging, and enable it during deployment
- You don't have complicated caching policies
- You don't have a complicated deployment system that relies on Web.config's caching syntax
- Ideal if you're using XDT web transformations already
All I did was created a new attribute 'DonutCache'.
[DonutCache]
public ActionResult HomePageBody(string viewName)
{
var model = new FG2HomeModel();
return View(viewName, model);
}
Unfortunately you can only initialize an [Attribute]
with a constant, so you need to initialize the attribute in its constructor. Note: This doesn't prevent you setting 'varyByParam' in the [DonutCache] declaration.
class DonutCacheAttribute : OutputCacheAttribute
{
public DonutCacheAttribute()
{
Duration = Config.DonutCachingDuration;
}
}
Here I'm just initializing the attribute from my web.config by means of a static property:
<appSettings>
<add key="DonutCachingDuration" value="5"/>
</appSettings>
public static class Config {
public static int DonutCachingDuration
{
get
{
return int.Parse(ConfigurationManager.AppSettings["DonutCachingDuration"]);
}
}
}
Then of course you can use the XDT web transformation's you're already using to change this value
精彩评论