【问题标题】:Caching ChildActions using cache profiles won't work?使用缓存配置文件缓存 ChildAction 不起作用?
【发布时间】:2011-06-11 08:48:14
【问题描述】:

我正在尝试在我的 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:持续时间必须是正数。

如果我不使用缓存配置文件,它会起作用。知道有什么问题吗?

【问题讨论】:

    标签: asp.net-mvc caching


    【解决方案1】:

    我在related question 上进行了一些挖掘并查看了 mvc 3 源代码,它们绝对不支持除了 DurationVaryByParam 之外的任何属性。他们当前实现的主要错误是,如果您不提供其中任何一个,您将收到一个异常告诉您提供它,而不是一个异常说您尝试使用的内容不受支持。另一个主要问题是,即使您在 web.config 中关闭缓存,它们也会缓存,这看起来很蹩脚而且不正确。

    我遇到的最大问题是它们使用了在视图和局部视图中都有效的相同属性,但实际上它可能应该是 2 个不同的属性,因为局部视图非常有限并且表现得非常不同,至少在当前的实现中是这样。

    【讨论】:

    【解决方案2】:

    我通过创建自定义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 中的一个位置。

    【讨论】:

    • 当我发布这个问题时,我以为我做错了什么,并没有怀疑这是一个框架问题。最后我采用了类似的解决方案。
    • 是的,同样的事情也发生在我身上。而且由于我能够制定出一个相对简单的解决方案,因此决定将 id 添加到这个旧线程中,以防它对其他人有所帮助。
    • 效果很好 - 我不得不覆盖 OnActionExecuting 并仅在 web.config 中启用缓存配置文件时调用 base.OnActionExecuting。否则在设置enabled="false" 时会再次出现可怕的“持续时间”错误。
    【解决方案3】:

    这是一个简单的方法:

    • 您的基本目标是能够在调试期间禁用缓存,并在部署期间启用它
    • 您没有复杂的缓存策略(这意味着您确实需要尊重 Web.config 的缓存设置)
    • 您没有依赖 Web.config 缓存语法的复杂部署系统
    • 如果您已经在使用 XDT web transformations 则非常理想
    • 您只是认为它已经可以工作,并且对它没有并且需要快速修复感到恼火!

    我所做的只是创建了一个新属性“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' - 这是目前唯一可用于缓存 Action 方法的其他属性。

    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(),以确保缓存设置得到遵守。

    【讨论】:

      【解决方案4】:

      在某些情况下,只创建第二个操作方法可能是合适的,并禁用由您的主要操作调用的缓存。

          /// 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],所以不是答案
      【解决方案5】:

      这对我有用。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多