ASP.NET页面输出缓存功能允许我们对页面的响应进行缓存,这样,无需执行整个页面,后续的请求也会被处理,即只返回缓存的输出。输出的缓存分两个级别:页面的整个和局部。页面缓存很智能,能够基于请求的URL、查询字符串、参数或自定义字符串来保持输出。
输出缓存的配置非常方便,即可通过@OutputCache指令以声明方式配置,也可通过HttpCachePolicy类以编程方式进行配置。
页面输出缓存只针对匿名内容。对缓存的页面请求在IIS6.0下,由IIS直接处理,在IIS5.0下,由ASP.NET工作进程处理。不论哪种情况,页面请求不会进入ASP.NET管道,而只有在管道中才能进行身份验证,因而这种策略阻止了对受保护内容的访问。
@OutputCache指令
要缓存页面输出,只需在页面顶部定义@OutputCache指令即可。该指令有两个必选属性:Duration和VaryByParam。示例:
该指令即可用于页面也可应用于用户控件。
OutputCache指令的属性列表如下:
页面输出缓存时间的选择
Duration属性指示缓存系统维护页面HTML编译版本的秒数。
输出缓存的实现取决于当前使用的ASP.NET进程模型。
如果使用IIS 5.0进程模型,对ASP.NET页面的任何请求都会交给工作进程,并分配给HttpApplication对象,由管理处理。ASP.NET管道中包含一个名为OutputCacheModule的HTTP模块,用于捕获两个与输出缓存有关的应用程序级事件:ResolveRequestCache和UpdateRequestCache。对于已被缓存的页面,该模块会通过ResolveRequestCache事件来缩短请求的处理过程。这个请求会被该HTTP模块截获,并将缓存中的页面副本返回给它。当带有@OutputCache指令的页面被生成时,OutputCacheModule会获取并存储它的输出,以备后用。该页面的输出会存储在ASP.NET Cache对象的一个槽中。页面中Duration属性的设置会用于配置ASP.NET运行库生成的HTTP响应过期策略。输出会被该模块缓存,并保存指定的秒数。在这期间,所有传入的与缓存页面匹配的请求,都会由该模块处理,而不会使其流经ASP.NET管道。
如果使用IIS 6.0进程模型,输出缓存机制是集成到Web服务器当中的。这样,在性能和响应速度方面,新版本占有一定的优势,这要归功于IIS 6.0的内核缓存功能。启用该功能会使IIS截获由ASP.NET生成的页面输出,随后,该页面输出的副本会由IIS内核进行缓存。传入的ASP.NET页面请求会由内核级的驱动程序(http.sys)进行筛选,检查缓存中是否有与之匹配的页面。如果有匹配缓存的输出,则会通过内核级代码将其返回给调用者,而无需调用工作进程和ASP.NET管道。
Duration属性值设定是否合理,取决于具体的应用程序,但一般不会超过60s。
关于内核缓存,有几点需要注意:首先,内核缓存只针对通过GET命令发出请求的页面,因而,内核缓存不能用于回发。其次,设置VaryByParam和VaryByHeader属性页面不会存储在内核缓存中。最后,对于由内核缓存处理的页面,ASP.NET Request/Cache性能计数器不会将其计算在内。
页面输出位置的选择
输出缓存的位置有多种,既可以在发出请求的客户端、服务器端,也可以位于中间的代理服务器中。下表列出了各种方案,这些值都是OutputCacheLocation枚举类型:
带有@OutputCache指令的页面还会生成一组HTTP标头(如Expires和Cache-Control)。下游的代理服务器(如Microsoft ISA Server)会理解这些标头的含义,并沿途缓存这些页面。这样,在页面输出被缓存期间,页面的请求未到达源Web服务器之前便会被处理。
具体来说,HTTP标头Expires用于指示某页面将在服务器端被更新的时间。在这个时刻到达前,浏览器发出的新请求会由本地资源和客户端的缓存处理,而不会与服务器进行往返交互。若指定了HTTP标头Cache-Control,且没有被设置为No-Cache,那么它的值通常为public 或private。private会防止代理服务器缓存当前页面,而由浏览器进行缓存。
为页面输出添加数据库依赖项
@OutputCache指令的SqlDependency属性对应于SqlCacheDependency类。当SqlDependency属性被设置为Database:Table格式的字符串时,Sql Server缓存依赖便会被创建。若该依赖关系被破坏,页面输出便失效。示例:
注意,这里的Northwind不是数据库的名称,而是配置文件<database>区段中节点的名称。我们可以在SqlDependency属性中指定多个Database:Table对,并用分号隔开。
我们可有两种方式使用户控件变为可缓存对象:通过@OutputCache指令或对用户控件代码添加PartialCaching特性:
public partial class CustomersGrid : UserControl
{
...
}
HttpCachePolicy类
HttpCachePolicy类是与@OutputCache指令对应的编程接口,它提供的方法能够直接设置与缓存相关的HTTP标头。此外,通过HttpResponse对象,还能在某种程序上对这些标头进行控制。
HttpCachePolicy类的属性
下表列出了HttpCachePolicy类的属性:
若缓存的页面有多个标头或参数,那么每种请求的HTTP标头或参数值的组合都会对应于一个版本的页面。
HttpCachePolicy类的方法
下表列出了HttpCachePolicy类的方法:
HttpCachePolicy类的大多数方法用于控制与浏览器缓存相关的HTTP标头的值。AddValidationCallback方法提供了一种机制,能够以编程方式在输出缓存将响应返回客户端之前,检查服务器缓存中页面输出(响应的有效性)。
服务器缓存验证回调
在通过ASP.NET缓存处理响应前,所有注册的处理程序都有机会验证缓存页面有效性。只要有一个处理程序将页面标记为无效,对应项便会从缓存项中移除,该请求会按常规方式处理。该回调函数的签名如下:
第一个参数表示请求上下文,第二个参数是应用程序需要传给处理程序的用户定义的数据。最后一个参数是HttpValidationStatus枚举值的引用,该回调函数会利用这个值返回验证的结果。该枚举值包括IgnoreThisRequest、Invalid和Valid。IgnoreThisRequest表示缓存的资源并没有失效,但请求仍会按常规方式处理;Invalid表示缓存已失效;Valid表示缓存可用,使用缓存的响应来处理请求。
缓存页面的多个版本
同一页面可以带有不同的参数,可以通过不同HTTP标头进行配置,也可以基于发出请求的浏览器生成不同的输出。
ASP.NET允许我们缓存一个页面响应的多个版本,可以通过GET和POST参数、HTTP标头、浏览器类型、自定义字符串和控件属性来区分不同版本。
通过参数进行区分
通过参数区分缓存,可以使用@OutputCache指令的VaryByParam属性,也可使用HttpCachePolicy类的VaryByParams属性。如以声明方式进行,采用以下语法:
VaryByParam属性是必选的。如果不希望指定用于区分缓存的参数,将其设置为None即可。如果要区分所有参数,则设置为星号(*)。若将VaryByParam设置为多个参数,输出缓存包含的请求文档会对应于每一种请求的参数,不同的参数须用分号隔开。GET查询字符串中的项和POST命令主体中设置的参数,都是区分页面输出的有效参数。
如果希望使用HttpCachePolicy类来定义缓存参数,首先,应分别通过SetExpires和SetCacheability方法设置页面的过期时间和可缓存性,随后再设置VaryByParams属性。示例:
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.VaryByParams[ " employeeid;lastname " ] = true ;
通过标头进行区分
VaryByHeader属性和HttpCachePolicy类的VaryByHeaders属性允许我们根据指定的若干HTTP标头的值来缓存页面的多个版本。
如果希望根据多个标头进行区分,在不同标头名称间添加分号即可。如果希望添加所有标头,将VaryByHeader属性设置为星号(*)即可。如,下面的声明表示,按浏览器的可接受语言进行缓存:
如果使用编程方式进行设置,可利用HttpCachePolicy类的VaryByHeaders属性,如下所示:
如果希望以编程方式根据所有HTTP标头来区分缓存中的页面,可使用HttpCacheVaryByHeader类的VaryByUnspecifiedParameters方法:
上述代码等价于将VaryByHeader属性设为星号。
通过自定义的字符串进行区分
@OutputCache指令中的VaryByCustom属性允许我们根据自定义的字符串来区分页面输出的不同版本。赋给VaryByCustom属性的字符串仅仅是区分页面输出的算法的描述。该字符串会被传给global.asax文件中的GetVaryByCustomString方法(如果存在的话),返回另一个针对请求的字符串。以下的示例根据请求页面的device类型来区分页面输出:
接下来,需要在global.asax文件中添加应用程序特定的GetVaryByCustomString方法:
{
if (custom == " device " )
return context.Request.Browser.Type;
return base .GetVaryByCustomString(context, custom);
}
只要自定义信息可通过HttpContext类获得,我们就可采用这种方法,而不能使用页面加载后才能获得的信息(如主题)。
ASP.NET页面局部的缓存
如果缓存整个页面不切实际,那么可以采用“部分缓存”。部分换代缓存利用的是ASP.NET用户控件的概念,即一种轻型的、内嵌的页面,继承了页面的多种特性。
Shared属性
默认情况下,不同页面不共享同一个可缓存用户控件的输出。每个页面会维护其自身的用户控件响应副本。这样是为为完全地分离,避免发生冲突。
为使不同的页面共享同一个用户控件的相同输出,我们需要在用户控件的@OutputCache指令中将Shared属性设为true。
可缓存页面局部的缓存
如果用户控件在可缓存的页面中被缓存,会是什么结果?
答案是,页面和控件会被分别缓存。也就是说,页面和控件最终的响应都会被缓存。然而,如果二者的缓存时间不同,则采用页面的设置。也就是说,仅当页面新刷新,用户控件才会被刷新。
处理页面中的可缓存用户控件应谨慎,带有@OutputCache指令的控件与常规控件不同,在试图访问可缓存控件时,它可能不存在。
CustomerGrid1.Country = " USA " ;
缓存配置文件
针对页面的@OutputCache指令支持CacheProfile字符串属性,该属性引用的是web.config文件<outputCacheProfiles>区段中的项:
< outputCacheSettings >
< outputCacheProfiles >
< add name ="..." enabled ="true|false" duration ="..."
location ="..." sqlDependency ="..." varyByCustom ="..."
varyByParam ="..." noStore ="true|false" />
</ outputCacheProfiles >
</ couputCacheSettings >
</ caching >
通过在add节点中定义带有名称的项,可以将页面中所有与缓存相关的设置存储在配置文件中。通过将多页面共用的设置存储在web.config文件中,并在每个页面中引用该名称,便不必为所有页面设置相同参数了:
缓存后替换
利用缓存后替换机制,可以在缓存整个页面的同时更新指定的区域。如,使用这种机制,AdRotator控件每次请求都可以变换不同的广告,即使宿主页面被缓存。
为使用缓存后替换,我们需要将<asp:substitution>控件设置要进行内容替换的位置,并将该控件的MethodName属性设置为一个回调方法,如下所示:
< h3 > The output you see has been generated at:
<% = DateTime.Now.ToString() %> and is valid for 30 seconds < h3 >
< hr />
This content is updated regularly
< h2 >< asp:Substitution ID ="Substitution1" runat ="server" MethodName ="WriteTimeStamp" /></ h2 >
< hr />
This is more static and cached content
< asp:Button runat ="server" Text ="Refresh" />
</ form >
MethodName属性必须被设置为静态方法的名称,而该方法的签名需与HttpResponseSubstitutionCallback委托的签名匹配:
{
return DateTime.Now.ToString();
}
我们还可以通过HttpResponse对象的WriteSubstitution方法,以编程方式建立缓存后替换:
上述调用会向响应中插入一个占位符,该占位符稍后会由上述回调的输出替换。
Substitution控件也可以用在不使用输出缓存的页面中。在这种情况下,替换回调会在呈现时调用,并生成响应。
出于对性能的考虑,我们应避免在替换回调中调用Substitution控件。否则,该回调函数将维护该控件的引用和包含该控件的页面的引用。结果导致在缓存的内容过其前,页面实例不能被垃圾回收。