【原文地址】Donut Hole Caching in ASP.NET MVC
【原文发表日期】 May 12, 2009
【译注:原文的题目是Donut Hole Caching in ASP.NET MVC。Donut就是面包圈的意思,Donut Hole就是面包圈中心的那个洞洞。Donut Caching是指:在整个页面中缓存大部分的页面而对一小块要实时更新的内容不进行缓存。而Donut Hole Caching则是指相反的情况,亦即:不缓存大部分的页面而只缓存一小部分特定的内容。既然是仅缓存一部分的内容,我就译成局部缓存了。而Donut Caching似乎在很多地方都译为:“甜圈缓存”,我就沿用了】
不久之前,我写了关于ASP.NET MVC中“甜圈缓存”的一些内容,也即,除了一小部分的信息,你需要缓存整个视图的大部分内容的情况。更技术化的用词也许应该是“缓存替换”(cache substitution),因为这里它用到了Response.WriteSubstitution这个方法,不过我觉得“甜圈缓存”确实是蛮形象的描述 -- 因为你需要缓存所有的东西,除了中间那一小部分内容。
不过,如果是反过来的情况应该怎么办呢?也就是说,如果你是想缓存中间那个窟窿,而不是周围那圈面包怎么办?
(我认为我们应该将所有软件工程的概念都用美味的食物来命名,你觉得如何?)
换句话说,假设你是想对视图中的一部分内容采用与其他所有内容不同的缓存策略(比如,不同的缓存有效时限),那该如何做呢?关于如何在ASP.NET MVC中实现这一点至今还没有很明确的介绍。
举例而言,Html.RenderPartial方法会忽略所有视图用户控件上的OutputCache指令。而另一方面,如果你使用MVC Futures包中Html.RenderAction这个方法来显示一个视图中内嵌的一个动作的输出内容时,你就可能会遇到这个问题:就是虽然只有这个动作有一个OutputCacheAttribute的属性,但实际上整个视图都被缓存了。
我今天对这个问题多了一些深入研究,发现当你给一个控件指定一个OutputCache的指令时(或者给一个页面),这个控件的输出缓存不是由它自己来控制的。相反,看起来是ASP.NET页面的编译系统参与了进来,接收了这个这个指令,并相应地做了缓存行为的处理。
用直白的话来说,这意味着下面我将展示的技巧只有在默认的WebFormViewEngine里才能工作,虽然我对于如何使之在所有的视图引擎(view engine)里工作也有些初始的想法。我只是想和ASP.NET产品组里的一些了解ASP.NET深层机制的成员再进一步交流一下以便确认实实在在准确的处理途径。
在使用默认的WebFormViewEngine的情况下,实现局部输出缓存其实还挺简单的。你只需通过直接通过声明(declaratively)往视图里加入一个ViewUserControl,然后将对RenderAction或RenderPartial的调用都放在这个ViewUserControl内部。如果你调用了RenderAction,那你还需要将相应动作的OutputCache属性去掉。
请记住ViewUserControl会继承他们所在的视图的ViewData。所以如果你使用了一个强类型的视图,你也必须在ViewUserControl的泛型类型参数中使用相同的类。
如果上一段东西你读得有点云山雾罩的话,那是时候来看一个实际的示例了。假设你如下的控制器动作:
public ActionResult Index() {
var jokes = new[] {
new Joke {Title = "Two cannibals are eating a clown"},
new Joke {Title = "One turns to the other and asks"},
new Joke {Title = "Does this taste funny to you?"}
};
return View(jokes);
}
假设你是想在视图里显示一系列的笑话标题。通常,你会先创建一个强类型的视图,然后在那个视图内部,我遍历模型(model)中的数据并打印出各个笑话的标题。
现在,我们还是创建一个强类型的视图,但这个视图包含一个ViewUserControl,来代替原来我们写的遍历模型并打印的代码(请注意为了简洁起见,我删去了Inherits属性值里的那些命名空间)
<%@ Page Language="C#" Inherits="ViewPage<IEnumerable<Joke>>" %>
<%@ Register Src="~/Views/Home/Partial.ascx" TagPrefix="mvc" TagName="Partial"
%>
<mvc:Partial runat="server" />
在这个控件(译注:即Partial.ascx)内部,我们将完成原来要在主视图中完成的工作,并且配置缓存设定。请注意这里我们的ViewUserControl的泛型类型和视图的类型是一致的:IEnumerable<Joke>。这使我们得以将原本在视图中的代码原封不动地移到这个控件中去。我们也同时在此处配置OutputCache指令。
<%@ Control Language="C#" Inherits="ViewUserControl<IEnumerable<Joke>>" %>
<%@ OutputCache Duration="10000" VaryByParam="none" %>
<ul>
<% foreach(var joke in Model) { %>
<li><%= Html.Encode(joke.Title) %></li>
<% } %>
</ul>
现在,这部分的视图将会被缓存,而其余视图中的内容则不会。而在这个ViewUserControl内部,我们可以随意调用RenderPartial或者RenderAction。
请注意如果你想用这个技巧来缓存RenderPartial的输出结果,这不会给你带来太多的效益,除非显示这个局部视图本身(而非其中的数据)的代价很高。
这是因为输出内容的缓存要直到视图显示的阶段才会发生,如果准备该局部视图背后的数据需要很复杂的运算的话,那你实际上并没有节省多少工作,因为给这个局部视图提供数据的动作方法(action method)还是会在每次页面被请求时运行并且重新创建所需的数据。
在这种情况下,你需要手动缓存这个局部视图的数据,从而免去每次都必须重新创建它们的工作。有一个可以考虑的疯狂的想法是(先公开下想法而已)是允许将输出缓存的元数据(metadata)和一部分视图的数据关联起来。这样,你就可以只创建一小部分专属于那个局部视图的数据,然后这个局部视图就会自动在此之上进行输出缓存。
这个思路可以进一步的优化联合起来使用,也就是通过某些方法声明那一小部分专属于局部视图的数据只需在输出缓存过期(expire)后才需要被重新创建,如此我们就不必在每个请求响应中反复创建它们了。
而在使用RenderAction的情况下,你确实可以得到所有输出缓存的好处,因为如果你的ViewUserControl已经被缓存了的话,你所使用的这个内联的动作方法就不会再被调用了。
我这里编写了一个小小的程序来演示这些概念的实现,以免我上文的介绍不够明了。Enjoy!
打印 | 张贴于 2009-05-21 15:16:25 | Tag:暂无标签
留言反馈