使用缓存配置文件来缓存ChildActions行不通?

18
我想在我的MVC应用程序中使用缓存配置文件来缓存子操作,但是我遇到了一个异常:“持续时间必须是正数。”
我的web.config看起来像这样:
<caching>
      <outputCache enableOutputCache="true" />
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="TopCategories" duration="3600" enabled="true" varyByParam="none" />
        </outputCacheProfiles>
      </outputCacheSettings>
</caching>

我的子操作大致像这样:

[ChildActionOnly]
[OutputCache(CacheProfile = "TopCategories")]
//[OutputCache(Duration = 60)]
public PartialViewResult TopCategories()
{
    //...
    return PartialView();
}

在视图中,我刚刚调用了@Html.RenderAction("TopCategories", "Category")

但是我收到了一个错误消息: 异常详细信息:System.InvalidOperationException:持续时间必须为正数。

如果我不使用缓存配置文件,它可以工作。你有什么想法是什么问题吗?

5个回答

17

我通过创建自定义的OutputCache属性解决了这个问题,手动从配置文件中加载DurationVarByCustomVarByParam

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;
    }
}
这种方法的优点是您仍然可以将所有配置文件保存在Web.config的一个地方。

当我发布这个问题时,我认为自己做错了什么,并没有怀疑这是一个框架问题。最终,我选择了类似的解决方案。 - frennky
是的,我也遇到了同样的问题。由于我能够找出一个相对简单的解决方案,因此决定将id添加到这个旧线程中以帮助其他人。 - Pablo Romeo
3
工作得非常好 - 尽管我不得不覆盖OnActionExecuting方法并且仅在web.config中启用缓存配置文件时才调用base.OnActionExecuting。否则,当将enabled="false"时,可怕的“Duration”错误将重新出现。 - marapet

17

我查了一个相关问题,并查看了mvc 3源代码,他们明确不支持除 Duration VaryByParam 之外的任何属性。他们当前实现的主要问题是,如果您没有提供这两者中的任何一个,将会抛出一个异常告诉您应该提供它,而不是抛出一个指示您所尝试使用的不受支持的异常。另一个主要问题是,即使在web.config中关闭缓存,他们也会进行缓存,这似乎非常糟糕和不正确。

我对所有这些的最大问题是,他们在视图和局部视图中都使用相同的属性,但实际上它可能应该是2个不同的属性,因为局部视图非常有限,并且行为有很大不同,至少在其当前实现中。


2
这里有一篇很好的文章,解释了这个问题:http://www.dotnetcurry.com/ShowArticle.aspx?ID=665 - frennky
我今天修复了这个问题并提交了一个拉取请求:http://aspnetwebstack.codeplex.com/SourceControl/network/forks/ssmith/OutputCacheAttributeBugfix/contribution/4100,同时还写了一篇文章,介绍如何自己修复此问题(通过拉取请求),而不仅仅是在SO上发表评论:http://ardalis.com/how-to-contribute-to-aspnet-yourself。 - ssmith
嘿,frennky,感谢你抽出时间并编写了这个Pull请求。我们已经接受并合并了它。但是需要注意的是:该Pull请求未解决web.config问题 - 我们将其作为错误https://aspnetwebstack.codeplex.com/workitem/1492进行跟踪。 - Yishai Galatzer

2

如果:

  • 您的基本目标是在调试期间禁用缓存,并在部署期间启用缓存
  • 您没有复杂的缓存策略(这意味着您真正需要遵守Web.config的缓存设置)
  • 您没有一个依赖于Web.config缓存语法的复杂部署系统
  • 如果您已经使用XDT Web转换
  • 您只是觉得它应该已经工作了,并且很烦恼它没有,需要快速修复!

我所做的就是创建了一个新属性'DonutCache'。

[DonutCache]
public ActionResult HomePageBody(string viewName)
{
    var model = new FG2HomeModel();

    return View(viewName, model);
}

我将缓存设置存储在Web.config文件中(使用新的自定义名称 - 以避免混淆)。

<appSettings>
    <add key="DonutCachingDuration" value="5"/>   <!-- debug setting -->
</appSettings>

我创建了一个简单的帮助方法来提取值。
public static class Config {
    public static int DonutCachingDuration
    {
        get
        {
            return int.Parse(ConfigurationManager.AppSettings["DonutCachingDuration"]);
        }
    }
}

很遗憾,您只能使用常量来初始化[Attribute],因此您需要在其构造函数中初始化属性(不幸的是,您不能只说[Attribute(Config.DonutCachingDuration)])。

注意:这不会阻止您在[DonutCache]声明中设置'varyByParam' - 这是目前可用于缓存操作方法的唯一其他属性。

class DonutCacheAttribute : OutputCacheAttribute
{
    public DonutCacheAttribute()
    {
        // get cache duration from web.config
        Duration = Config.DonutCachingDuration;
    }
}

只需使用XDT Web变换,即可轻松部署具有更长价值的内容。
  <add key="DonutCachingDuration" value="120" 
       xdt:Locator="Match(key)" xdt:Transform="Replace"/>

提示: 你可能想在你的部分视图中加入 @DateTime.Now.ToString() 来确保缓存设置得到了遵守。


-1
在某些情况下,创建第二个操作方法可能是合适的,禁用缓存,并由您的主要操作调用。
    /// 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();
    }

当你需要使用Html.Action时,只需调用_Foo而不是Foo。

@Html.Action("_Foo", "Bar").ToString();

你可以依靠父页面来进行缓存。如果这不合适(因为你不想缓存整个页面)- 你可以使用我在其它回答中提到的 'DonutCacheAttribute'。


这不允许使用[ChildActionOnly],因此它不是一个答案。 - YEH

-1

对我来说可以。

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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接