Caching ChildActions using cache profiles won't work?
Asked Answered
E

5

18

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?

Emissivity answered 18/1, 2011 at 21:0 Comment(0)
R
17

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.

Revengeful answered 26/1, 2011 at 0:5 Comment(3)
Here's a nice article that explains this problem: dotnetcurry.com/ShowArticle.aspx?ID=665Emissivity
I fixed this and submitted a pull request today: aspnetwebstack.codeplex.com/SourceControl/network/forks/ssmith/… as well as writing an article on how to fix this yourself (via pull request) in addition to just commenting about it on SO: ardalis.com/how-to-contribute-to-aspnet-yourselfElectrodynamics
Hey frennky, thanks for taking the time and writing up this Pull request. We accepted and merged it. Important note though: The pull request does not address the web.config issue - We are tracking it as bug aspnetwebstack.codeplex.com/workitem/1492Weighbridge
L
17

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.

Laporte answered 13/12, 2012 at 18:39 Comment(3)
When I posted the question, I thought I was doing something wrong and didn't suspect it's a framework issue. In the end I went with a similar solution.Emissivity
Yup, same thing happened to me. And since I was able to work out a relatively simple solution decided to add id to this old thread in case it helps somebody else.Laporte
Works great - I had to override OnActionExecuting though and call base.OnActionExecuting only when the cache profile was enabled in the web.config. Otherwise the dreaded "Duration"-error re-appeared when setting enabled="false".Overly
L
2

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.

Lactic answered 2/2, 2011 at 0:8 Comment(0)
L
-1

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.

Lactic answered 11/3, 2011 at 5:37 Comment(1)
This doesn't allow the use of [ChildActionOnly], so it's not an answerThallus
E
-1

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);
    }
}
Estriol answered 4/7, 2013 at 8:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.