Caching ChildActions using cache profiles won't work?
I'm trying to use cache profiles for caching child actions in my mvc application, but I get an exception: Duration must be a positive开发者_运维问答 number.
My web.config looks like this:
<caching>
<outputCache enableOutputCache="true" />
<outputCacheSettings>
<outputCacheProfiles>
<add name="TopCategories" duration="3600" enabled="true" varyByParam="none" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
And my child action something like this:
[ChildActionOnly]
[OutputCache(CacheProfile = "TopCategories")]
//[OutputCache(Duration = 60)]
public PartialViewResult TopCategories()
{
//...
return PartialView();
}
Inside a view I just call @Html.RenderAction("TopCategories", "Category")
But I get an error: Exception Details: System.InvalidOperationException: Duration must be a positive number.
If I don't use cache profile it works. Have an idea what's the problem?
I did some digging on a related question and looking at mvc 3 source, they definitely don't support any attribute other than Duration and VaryByParam. The main bug with their current implementation is that if you don't supply either one of these you will get an exception telling you to supply that, instead of an exception say that what you tried to use is not supported. The other major issue was that they will cache even if you turn off caching in the web.config, which seems really lame and not right.
The biggest issue I had with it all is that they are using the same attribute which works in both views and partial views, but in reality it should probably be 2 different attributes since the partial view is so limited and behaves a lot differently, at least in it's current implementation.
I got around the problem by creating a custom OutputCache
attribute, that manually loads the Duration
, VarByCustom
and VarByParam
from the profile:
public class ChildActionOutputCacheAttribute : OutputCacheAttribute
{
public ChildActionOutputCacheAttribute(string cacheProfile)
{
var settings = (OutputCacheSettingsSection)WebConfigurationManager.GetSection("system.web/caching/outputCacheSettings");
var profile = settings.OutputCacheProfiles[cacheProfile];
Duration = profile.Duration;
VaryByParam = profile.VaryByParam;
VaryByCustom = profile.VaryByCustom;
}
}
The advantage of this approach is that you get to still keep all your profiles in just one place in the web.config.
Here's a simple way 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 (that mean you truly need to respect Web.config's caching settings)
- 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
- You just assumed it would already work and are annoyed that it didn't and need a quick fix!
All I did was created a new attribute 'DonutCache'.
[DonutCache]
public ActionResult HomePageBody(string viewName)
{
var model = new FG2HomeModel();
return View(viewName, model);
}
I store my caching setting in Web.config (under a new custom name - so as to avoid confusion).
<appSettings>
<add key="DonutCachingDuration" value="5"/> <!-- debug setting -->
</appSettings>
I created a simple helper method to pull the value out.
public static class Config {
public static int DonutCachingDuration
{
get
{
return int.Parse(ConfigurationManager.AppSettings["DonutCachingDuration"]);
}
}
}
Unfortunately you can only initialize an [Attribute]
with a constant, so you need to initialize the attribute in its constructor (you cant just say [Attribute(Config.DonutCachingDuration)]
unfortunately).
Note: This doesn't prevent you setting 'varyByParam' in the [DonutCache] declaration - which is currently the only other property that is usable for caching of Action methods.
class DonutCacheAttribute : OutputCacheAttribute
{
public DonutCacheAttribute()
{
// get cache duration from web.config
Duration = Config.DonutCachingDuration;
}
}
Just use an XDT web transformation's and you're ready to deploy with a longer value.
<add key="DonutCachingDuration" value="120"
xdt:Locator="Match(key)" xdt:Transform="Replace"/>
Tip: You'll probably want to stick a @DateTime.Now.ToString()
in your partial view to make sure the cache setting is being respected.
In some cases it may be appropriate to just create a second action method, with caching disabled that is called by your primary action.
/// Use this for normal HTTP requests which need to be cached
[OutputCache(CacheProfile = "Script")]
public ContentResult Foo(string id)
{
return _Foo(id);
}
/// Use this for Html.Action
public ContentResult _Foo(string id)
{
return View();
}
When you need Html.Action
you just call _Foo instead of Foo.
@Html.Action("_Foo", "Bar").ToString();
You can then rely on the parent page to do the caching. If this isn't appropriate (because you don't want to cache the entire page) - you can use the 'DonutCacheAttribute' from my other answer.
That works for me.
public class ChildActionOutputCacheAttribute : OutputCacheAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction && !string.IsNullOrWhiteSpace(CacheProfile))
{
lock (this.GetType())
{
if (!string.IsNullOrWhiteSpace(CacheProfile))
{
// OutputCacheAttribute for child actions only supports
// Duration, VaryByCustom, and VaryByParam values.
var outputCache = (OutputCacheSettingsSection)WebConfigurationManager.GetSection("system.web/caching/outputCacheSettings");
var profile = outputCache.OutputCacheProfiles[CacheProfile];
if (profile.Enabled)
{
Duration = profile.Duration > 0 ? profile.Duration : Duration;
VaryByCustom = string.IsNullOrWhiteSpace(profile.VaryByCustom)
? VaryByCustom : profile.VaryByCustom;
VaryByParam = string.IsNullOrWhiteSpace(profile.VaryByParam)
? VaryByParam : profile.VaryByParam;
}
CacheProfile = null;
}
}
}
base.OnActionExecuting(filterContext);
}
}
精彩评论