Asp.Net MVC基础控制器中的Outputcache不起作用

10

我在使用asp.net mvc基础控制器中的输出缓存属性,但由于它在OnActionExecuting中,因此每次都会被调用。是否有选项仅调用该方法一次以加载所有默认值?

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    GetDefaults();
    base.OnActionExecuting(filterContext);
}

[OutputCache(Duration = 60000)]
private ActionResult GetDefaults()
{
    //code to load all my default values
    // Viewdata values used in all pages in my aplication
    // Viewdata values used in all pages in my aplication
    // Viewdata values used in all pages in my aplication
    return null;
}

有没有其他最佳实践来加载所有页面的默认值并将它们缓存起来?


1
又一个不要在MVC中使用基础控制器的原因!如果您有共享内容需要缓存,无论哪个页面先到达它,都应该使用全局过滤器来缓存。OutputCache是用于...好吧...输出的。如果您需要缓存数据,则应使用HttpContext.CacheSystem.Runtime.Caching.MemoryCache - NightOwl888
1
你正在混淆输出缓存和应用程序缓存,这与方法是否在基础控制器中无关。 - haim770
这里的应用程序缓存是什么?你能否添加更多细节。 - Kurkula
1
https://dev59.com/4YDba4cB1Zd3GeqPAimh#23943993 - Amirhossein Mehrvarzi
3个回答

5
如果你只是想缓存数据,那么OutputCache不是正确的机制。 OutputCache可用于缓存操作方法生成的HTML,以便无需重新生成。如果要缓存数据,可以使用HttpContextBase.Cache轻松实现。
此外,我建议不要使用基控制器类。这肯定意味着您将把feature afeature bfeature c的逻辑混合在同一个基础控制器中-您正在创建一个God对象。MVC有更好的方法-过滤器,它们可以注册为所有操作运行并且可与过滤器一起使用在特定操作上。
虽然可能没有任何理由将全局过滤器用于缓存,因为通常数据是在请求点被缓存的,但我已经创建了一个演示如何完成它的演示。请注意,缓存是一个非常广泛的主题,可以通过多种方式来完成,但是因为您没有提供任何关于正在缓存什么、数据源是什么或如何使用它的信息,所以我只展示这个可能的方法。

MyCacheFilter

在这里我们有一个操作过滤器执行缓存。由于操作过滤器保证在视图之前运行,因此可以通过这种方式实现。 MyCacheFilter执行3件事情:
  1. 从缓存中读取数据
  2. 检查数据是否存在,如果不存在,则重新加载缓存
  3. 将对数据对象的引用添加到请求缓存中,在应用程序中的任何其他位置(包括视图)中都可以访问它。
public class MyCacheFilter : IActionFilter
{
    /// <summary>
    /// The cache key that is used to store/retrieve your default values.
    /// </summary>
    private static string MY_DEFAULTS_CACHE_KEY = "MY_DEFAULTS";

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // Do nothing
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var cache = filterContext.HttpContext.Cache;

        // This method is called for each request. We check to ensure the cache
        // is initialized, and if not, load the values into it.
        IDictionary<string, string> defaults = 
            cache[MY_DEFAULTS_CACHE_KEY] as IDictionary<string, string>;
        if (defaults == null)
        {
            // The value doesn't exist in the cache, load it
            defaults = GetDefaults();

            // Store the defaults in the cache
            cache.Insert(
                MY_DEFAULTS_CACHE_KEY,
                defaults,
                null,
                DateTime.Now.AddHours(1), // Cache for exactly 1 hour from now
                System.Web.Caching.Cache.NoSlidingExpiration);
        }

        // Caching work is done, now return the result to the view. We can
        // do that by storing it in the request cache.
        filterContext.HttpContext.SetMyDefaults(defaults);
    }

    private IDictionary<string, string> GetDefaults()
    {
        // You weren't specific about where your defaults data is coming from
        // or even what data type it is, but you can load it from anywhere in this method
        // and return any data type. The type returned should either by readonly or thread safe.
        var defaults = new Dictionary<string, string>
        {
            { "value1", "testing" },
            { "value2", "hello world" },
            { "value3", "this works" }
        };


        // IMPORTANT: Cached data is shared throughout the application. You should make
        // sure the data structure that holds is readonly so it cannot be updated.
        // Alternatively, you could make it a thread-safe dictionary (such as ConcurrentDictionary),
        // so it can be updated and the updates will be shared between all users.
        // I am showing a readonly dictionary because it is the safest and simplest way.
        return new System.Collections.ObjectModel.ReadOnlyDictionary<string, string>(defaults);
    }
}

使用MyCacheFilter

为了使用我们的缓存过滤器并确保在显示任何视图之前填充缓存,我们将其注册为全局过滤器。全局过滤器非常适用于将不同功能模块化到不同的类中,这样它们就可以很容易地维护(不像基础控制器)。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Add our MyCacheFilter globally so it runs before every request
        filters.Add(new MyCacheFilter());
        filters.Add(new HandleErrorAttribute());
    }
}

HttpContextBaseExtensions

为了使缓存数据对应用程序更加类型安全,这里提供了几个实用的扩展方法。

/// <summary>
/// Extensions for convenience of using the request cache in views and filters.
/// Note this is placed in the global namespace so you don't have to import it in your views.
/// </summary>
public static class HttpContextBaseExtensions
{
    /// <summary>
    /// The key that is used to store your context values in the current request cache.
    /// The request cache is simply used here to transfer the cached data to the view.
    /// The difference between the request cache (HttpContext.Items) and HttpContext.Cache is that HttpContext.Items
    /// is immediately released at the end of the request. HttpContext.Cache is stored (in RAM) for the length of
    /// the timeout (or alternatively, using a sliding expiration that keeps it alive for X time after 
    /// the most recent request for it).
    /// 
    /// Note that by using a reference type
    /// this is very efficient. We aren't storing a copy of the data in the request cache, we
    /// are simply storing a pointer to the same object that exists in the cache.
    /// </summary>
    internal static string MY_DEFAULTS_KEY = "MY_DEFAULTS";


    /// <summary>
    /// This is a convenience method so we don't need to scatter the reference to the request cache key
    /// all over the application. It also makes our cache type safe.
    /// </summary>
    public static string GetMyDefault(this HttpContextBase context, string defaultKey)
    {
        // Get the defaults from the request cache.
        IDictionary<string, string> defaults = context.Items[MY_DEFAULTS_KEY] as IDictionary<string, string>;

        // Get the specific value out of the cache that was requested.
        // TryGetValue() is used to prevent an exception from being thrown if the key doesn't
        // exist. In that case, the result will be null
        string result = null;
        defaults.TryGetValue(defaultKey, out result);

        return result ?? String.Empty;
    }

    /// <summary>
    /// This is a convenience method so we don't need to scatter the reference to the request cache key
    /// all over the application. It also makes our cache type safe.
    /// </summary>
    internal static void SetMyDefaults(this HttpContextBase context, IDictionary<string, string> defaults)
    {
        context.Items[MY_DEFAULTS_KEY] = defaults;
    }
}

用法

最后,我们需要使用视图中的数据。由于HttpContextBase对象上有扩展方法,所以我们只需要通过视图访问它并调用我们的扩展方法即可。

<p>
    value1: @this.Context.GetMyDefault("value1")<br />
    value2: @this.Context.GetMyDefault("value2")<br />
    value3: @this.Context.GetMyDefault("value3")<br />
</p>

我已经在这个GitHub存储库中创建了一个可工作的演示解决方案:点击此处
再次强调,这不是唯一的方法。您可能希望根据您的应用程序进行一些修改。例如,您可以使用ViewData将数据返回到视图,而不是使用HttpContextBase.Items。或者,您可能希望摆脱全局过滤器,并将缓存模式移到单个扩展方法中,从缓存中加载/返回数据。确切的解决方案取决于您在问题中未提供的要求。

4
您可以在Global.asax.cs中使用Application_Start()或Session_Start()来存储数据,具体取决于您希望在应用程序启动时还是每个会话开始时刷新数据。这将根据您的应用程序需要进行决定。
如果您需要对每个操作执行某些操作,则像您所做的那样拥有一个基础控制器是很好的选择。最常见的是[Authorize]过滤器,出于安全考虑,您需要为每个操作执行此操作。
另一种方法是编写自己的ActionFilterAttribute并执行所需的缓存部分。然后,您只需要将此新Action过滤器添加到需要执行此缓存的任何操作中。请参阅此处以了解如何执行此操作:https://msdn.microsoft.com/en-us/library/dd410056(v=vs.98).aspx 但由于您希望数据仅加载一次,因此我认为Action过滤器可能不是执行缓存的正确位置。

2

这里无法利用缓存引擎。原因很明显,如果你考虑缓存是如何工作的,当调用一个动作时,调用它的永远不是用户代码,而是MVC框架。这样就有机会应用缓存。在您的示例中,用户代码直接调用方法,没有间接性,只是普通的调用-没有涉及缓存。


有没有其他最佳实践来加载所有页面的默认值并将它们缓存起来? - Kurkula

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