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 li
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.
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.
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.
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);
}
}
Here's a simple way if :
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.