位置:海鸟网 > IT > ASP.NET >

ASP.NET 2.0 的内部变化

Jayesh Patel、Bryan Acker 和 Robert McGovern

Infusion Development

适用范围:

Microsoft ASP.NET 2.0

摘要:尽管 ASP.NET 2.0 与 ASP.NET 1.1 完全向后兼容,但还是为 ASP.NET 带来了大量的内部变化,包括代码模型、编译、页面生命周期等的变化。本文将概括介绍这些变化。



本页内容
引言
代码模型
编译
完全运行时编译(代码目录)
页面生命周期
可扩展性
高级缓存技术
性能
结论

引言
对于专业的 ASP.NET 开发人员来说,与 ASP.NET 2.0 有关的重要问题是内部发生了哪些变化。尽管新功能很有趣而且学起来很有意思,但对于真正想掌握这一技术的开发人员来说,ASP.NET 核心结构发生的变化才是最吸引他们的地方。在本白皮书中,我们将介绍自版本 1.x 以来,ASP.NET 2.0 内部结构发生了什么样的变化。

本白皮书介绍的主题对那些关注性能的开发人员以及寻求如何优化应用程序的技术设计师非常有用。具体来说,我们将介绍有关代码模型、编译、页面生命周期、可扩展性、性能和缓存的主要问题。

本文中的许多示例要求您对 ASP.NET、Visual Basic .NET 和/或 C# 语法有相当程度的了解。本文还在适当的地方提供了参考文档,就某些特定的主题展开深入的讨论。

返回页首
代码模型
也许 ASP.NET 2.0 内部工作方式最明显的变化是 ASP.NET Web 页面的创建方式的变化。本节将介绍内含代码模型发生的变化以及这些变化对 ASP.NET 开发的影响。

ASP.NET 1.x 中的代码模型
在 ASP.NET 1.x 中,供开发人员开发 Web 窗体的主要选择有两个。首先,开发人员可以参照传统的 ASP 模型并直接在 ASPX 页面中编写代码。此过程称为“内嵌代码”,它非常适用于简单的命令。然而,对于更复杂的代码而言,编写内嵌代码将为读取混合了表示 (HTML) 和功能(代码)的 Web 页面带来困难。在 ASP.NET 中,为了帮助解决这个问题,已更改了默认的编码方法。您可以在单独的、只包含代码的文件(称为“内含代码”文件)中编写业务逻辑和事件处理代码。内含代码模型将只包含代码的文件与包含表示标记的 ASPX 文件链接起来。通过将代码与表示相分离,开发小组可以让设计人员处理演示文件,而让开发人员处理代码文件,从而提高开发小组的工作效率。



图1:ASP.NET 1.x 代码模型

内含代码模型面临的主要困难在于如何将内含代码文件与 ASPX 页面保持同步。尽管从编程意义上来讲 ASPX 页面是从内含代码文件继承而来的,但实际上这两个文件是通过更复杂的关系联系在一起的。

有关 ASP.NET 1.x 中的内含代码模型的详细信息,请参见 MSDN Library 文章 Web Forms Code Model(英文)。

继承的复杂性
ASP.NET 的设计模式就是让开发人员使用 Microsoft Visual Studio .NET 将控件拖放到 ASPX 页面中。Visual Studio 然后将在内含代码文件中自动生成适当的支持代码。如果控件已被添加到 ASPX 页面中,则必须在内含代码文件中添加新代码。换句话说,尽管 ASPX 页面继承自内含代码文件,但实际上 ASPX 页面推动了内含代码文件的设计。

编译的复杂性
第二个同步问题就是如何编译文件。所有的内含代码文件以及所有的支持类都被编译到一个程序集中,并存储在 Web 应用程序的 /bin 目录中。应用程序的编译工作是在部署工作之前进行的。而另一方面,ASPX 页面也是在第一次被请求时在运行时编译的。ASP.NET 运行库实际上将 ASPX 页面编译到该页面自己的临时程序集中。

此过程的问题在于,无需更新内含代码程序集即可更改 ASPX 页面。也就是说,开发人员可以在部署后更改某个属性或更改 ASPX 页面上某个控件的类型,而无需更新内含代码文件,也不用重新编译应用程序的程序集。出现这种情况时,由于内含代码文件与所关联的 ASPX 页面不匹配,应用程序可能会遇到意外的错误。

ASP.NET 2.0 中的代码模型
ASP.NET 2.0 继续提供内嵌代码和内含代码模型。从内嵌代码模型的角度来说,除了 Microsoft Visual Studio 2005 支持单文件开发的方式有所变化外,基本上没做什么改动。有关 Visual Studio 2005 中的变化及其如何处理内嵌代码的详细信息,请参阅这篇文章(英文)。

ASP.NET 2.0 通过改变内含代码文件的本质解决了内含代码模型的继承和编译问题。在 ASP.NET 2.0 中,内含代码文件不再是 System.Web.UI.Page 类的完整实现。相反,内含代码文件是一个新的构造函数,称为“局部类”。局部类包含所有用户定义的代码,但是不包含 Visual Studio .NET 在 ASP.NET 1.x 中自动生成的任何管线和连接代码。请求包含新的内含代码文件的 ASPX 页面时,ASP.NET 2.0 运行库实际上将把 ASPX 页面和局部类合并成一个类,而不是两个单独的类。



图 2:ASP.NET 2.0 中的内含代码模型

局部类使用新的关键字(在 Visual Basic 中为 Expands,在 C# 中为 Partial)指示在运行时将该类中的代码与另一个类中的代码相合并。类似地,ASPX 页面使用新的指令(称为 compilewith)指示该页面与内含代码文件之间的关联。

比较内含代码文件
如果您熟悉传统的 ASP.NET 1.x 内含代码文件,您应该知道 Visual Studio 将插入自动生成的控件声明和初始化代码。这种自动生成的代码是内含代码文件与 ASPX 文件之间双向同步的直接结果。带有标签的典型 ASPX 页面具有一个对应的内含代码文件,该文件由许多自动生成的文本行构成。

列表 1:ASP.NET 1.x 中的内含代码文件

namespace WebApplication1
{
public class WebForm1 :System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label Label1;
private void Page_Load(object sender,
System.EventArgs e) { }
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
}

自动生成的代码不仅定义标签(以粗体显示的行),还声明新的事件(页面加载)并自动将该事件绑定到自动生成的方法包装程序 (Page_Load())。

比较而言,同一个 ASP.NET 页面在 ASP.NET 2.0 中生成的内含代码文件更简洁。

列表 2:ASP.NET 2.0.x 中的内含代码文件

namespace ASP {
public partial class Webform1_aspx
{
}
}

开发人员可以自动访问 Label1,并根据需要添加事件。例如,可以添加 Page_Load 事件以初始化标签。

列表 3:在新的内含代码文件中添加事件

namespace ASP {
public partial class Webform1_aspx
{
void Page_Load(object sender, EventArgs e)
{
Label1.Text = "Hello ASP.NET 2.0";
}
}
}

事件语法可通过 Visual Studio .NET 生成。生成的内含代码文件更短,而且不包含任何自动生成的代码。ASP.NET 运行库会自动将内含代码文件中的事件绑定到 ASPX 页面上的控件。换句话说,ASP.NET 运行库现在可以自动执行代码生成,而这原本是由 Visual Studio 执行的。

继承的复杂性
新的内含代码模型大大降低了继承的复杂性。由于 ASPX 页面不是直接继承自内含代码文件,因此内含代码文件不再需要定义并支持 ASPX 页面上定义的所有控件。类似地,内含代码文件可以自动访问 ASPX 页面上的任何控件,而无需声明代码(声明代码在 ASP.NET 1.x 中是必需的)。这一切之所以能够实现,是因为 ASP.NET 运行库可以将所需的声明代码和事件绑定代码自动插入到最终编译的文件中。因为这些工作由运行库来完成,所以代码开发人员和 Web 开发人员都无需再担心这个问题。

在设计期间,链接将由 Visual Studio 2005 来维护。Visual Studio 环境将利用 ASP.NET 运行库编译段来确保代码开发人员和 Web 开发人员可以同步工作。

编译的复杂性
由于新的内含代码文件与 ASPX 页面链接在一起并在运行时被编译到一个完整的类中,因此编译的复杂性不复存在。也就是说,内含代码文件将自动与 ASPX 页面保持同步。即使新的编译模型也可能包含未同步的代码,但由于产生的异常非常清楚,因此可以快速找到问题的根源。

返回页首
编译
自从在 ASP.NET 1.x 中引入页面模型后,ASP.NET Web 页面的编译过程一直被分为两个阶段。首先,将内含代码文件和其他支持类编译到一个程序集中,然后在运行时编译各个 ASPX 文件。尽管此模型有很多优点,但它还有几个缺点。ASP.NET 2.0 为基本模型提供了几种替代方案,从而扩大了编译的选择范围,您可以根据特定需要进行选择。

ASP.NET 1.x 中的编译
ASP.NET 1.x 中的主要编译模型生成了一个应用程序程序集(包含所有已编译的内含代码文件和其他源代码)和一个临时程序集(专门为请求的每个 ASPX 页面而创建)。在某些情况下,编译器优化(例如批处理)可能会导致临时 ASPX 页面被编译到同一个程序集中。在任何情况下,都会将每个 ASPX 页面编译到一个临时程序集中,因此该页面可以加载到 ASP.NET 运行库中。



图 3:ASP.NET 1.x 中的编译

尽管此模型有很多优点,但它还有两个主要的缺点。首先,必须以用户可以阅读的格式将 ASPX 页面部署到 Web 站点上。如果开发人员使用“内嵌代码”模型,那么还需要将某些甚至所有业务逻辑部署到生产服务器上。尽管 IIS 和 ASP.NET 被配置为不显示原始 ASPX 页面,但是狡猾的攻击者仍然可以通过对 Web 服务器开放的任何入口访问这些文件。其次,任何人首次请求 Web 页面时,响应都会比正常情况下慢,因为 ASP.NET 运行库必须编译 ASPX 页面。

在这一过程中,开发人员唯一能够控制的就是是否对 ASPX 页面进行批编译。在 ASP.NET 1.x 中,您可以通过修改 web.config 文件中的 <compilation> 标记来配置批编译。

列表 4:配置批编译

<compilation
batch="true|false"
batchTimeout="number of seconds"
maxBatchSize="maximum number of pages per batched compilation"
maxBatchGeneratedFileSize="maximum combined size (in KB) of the
generated source file per batched compilation"
</compilation>

首次请求 Web 页面时,批编译将通过延长启动时间来缩短加载时间。批编译的另一个优点是将所有的 ASPX 文件编译到一个临时程序集中,而不是将每个页面编译到一个临时程序集中。

ASP.NET 2.0 中的编译
ASP.NET 2.0 为 Web 应用程序提供了四种不同的编译模型:

• 普通 (ASP.NET 1.x) - 在普通的 ASP.NET Web 应用程序中,内含代码文件被编译到一个程序集中并存储在 /bin 目录中。Web 页面 (ASPX) 按需进行编译。此模型适用于大多数 Web 站点。但是,编译过程将导致第一次请求 ASP.NET 页面时的响应速度比后续请求慢。ASP.NET 2.0 继续支持这种编译模型。

• 批编译 - 在 ASP.NET 2.0 中,您可以使用一个 URL 请求对任何应用程序进行批编译。就像在 ASP.NET 1.x 中一样,批编译消除了首次请求页面时的延迟,但延长了启动时间。此外,批编译仍然要求在部署之前编译内含代码文件。

• 部署预编译 - 这是 ASP.NET 2.0 提供的一项新功能,允许在部署之前完整地编译项目。在完整编译中,根据应用程序的大小和编译设置,可以将所有的内含代码文件、ASPX 页面、HTML、图形资源和其他后端代码编译到一个或多个可执行的程序集中。这些程序集包含 Web 站点的所有已编译代码,并原封不动地复制了资源文件和配置文件。这种编译方法提供了最好的性能和安全性,但代价是您无法在部署后修改 Web 站点。如果您正在部署高度可见或高度安全的 Web 站点,那么此选项是最终部署的最佳选择。但是,如果您正在构建在本地 Intranet 上运行的小站点,而且站点更改频繁,那么完整的预编译可能是多余的。

• 完整的运行时编译 - 与部署预编译相反的另一个极端情况是,ASP.NET 2.0 提供在运行时编译整个应用程序的新机制。也就是说,您可以将未编译的内含代码文件和其他关联的代码放入新的代码目录中,在运行时根据这些文件生成程序集并让 ASP.NET 2.0 创建并维护对这些程序集的引用。此选项为更改 Web 站点的内容提供了最大的灵活性,但代价是将未编译的代码存储在服务器上。


您可以根据实际的情况和需要选择最佳的编译选项,但编译模型仍然是灵活的。即使您选择使用代码目录来存储内含代码文件,也仍然可以使用完整编译这种方法来部署应用程序。

批编译
web.config 批编译设置在 ASP.NET 2.0 中仍然有效。批编译的优点在于,页面可以立即显示给第一个用户,并且可以在批编译过程中检测到 ASPX 页面中的任何错误。但是,批编译确实会延长应用程序的启动时间,而且必须内置在 web.config 文件中。

部署预编译
部署预编译允许您创建一个或多个程序集,作为 Web 站点的可执行版本。生成的程序集包含 Web 站点的已编译代码。HTML 页面、资源、配置文件和 ASPX 页面将被分别复制。

部署预编译需要使用 aspnet_compiler.exe 命令行实用程序,此实用程序将创建目标部署目录,其中包含用于存储程序集的 /bin 目录以及各种 ASPX 页面的 Stub 文件。还可以使用此实用程序执行在位预编译,与调用“魔术页面”的行为类似。Stub 文件使用 ASPX 页面的名称,但包含调用已编译程序集的简单代码。换句话说,ASPX 页面是简单的空壳,而不是功能完善的页面。

通过在部署之前预编译 Web 站点,安全性将大大提高,因为如果不对程序集进行反编译,将无法访问任何代码。为了增强保护,您可以打乱生成的程序集,使您的 Web 应用程序更安全。部署预编译的主要缺点在于您必须在部署之前完成这些操作,而且不能在部署后更改 Web 站点。如果要进行更改,必须重新编译 Web 站点并重新部署。

对于大多数主要的 Web 应用程序而言,部署预编译选项是实现部署的首选机制,因为它可以减少在 Web 服务器上部署的原始代码数量,并能提供最高的安全性。可以将增加的进程内置在通常的开发/测试/部署周期中,而不会严重降低工作效率。

返回页首
完全运行时编译(代码目录)
在上述三种编译方法中,您必须在部署之前编译所有的代码文件(内含代码和支持类)。ASP.NET 2.0 中提供了代码目录。

代码目录是一个特殊的目录,用于存放未编译的类。在运行时,ASP.NET 运行库会将此目录中的内容编译到一个程序集中,该程序集将由应用程序中的 ASPX 页面自动引用。换句话说,使用代码目录,就无需为支持代码创建和引用单独的程序集。代码目录的优点在于,您无需完整地编译项目即可部署,从而降低了不匹配的可能性。但缺点是,您可能需要将未编译的代码存储在服务器上。

此选项特别适用于不需要大量支持代码(不管是以内含代码文件还是外部对象的格式)的 ASP.NET 应用程序。对于简单的应用程序而言,快速部署和测试系统的能力要比更稳健的编译方法更具优势。

返回页首
页面生命周期
ASP.NET 2.0 在 ASP.NET 页面的生命周期方面有两个主要的变化。首先,ASP.NET 2.0 提供了新的事件以支持新功能,这些功能包括母版页、个性化和集成的移动设备支持。其次,ASP.NET 2.0 引入了跨页发送 Web 窗体的技术。

新事件
与 ASP.NET 1.x 相比,ASP.NET 2.0 提供了更精确的页面生命周期方法堆栈。这些新增的方法为 Web 开发人员提供了更高级别的控制。可以通过任何 ASP.NET 页面上的“Page”对象访问这些事件。

表 1 显示了全面的方法列表。“方法”列显示了实际的事件方法名称,“活动”列指示事件是始终处于活动状态还是仅在 PostBack 操作期间处于活动状态。例如,可以使用新方法 TestDeviceFilter 来确定哪个设备筛选器可用,并使用此信息决定如何显示页面。换句话说,新方法 LoadControlState 仅在回发期间调用。可以替代此方法(与 SaveControlState 结合使用),以创建用于在回发期间保存和恢复控件状态的替换序列化方案。

表 1:页面生命周期方法
方法 活动
Constructor
始终

Construct
始终

TestDeviceFilter
始终

AddParsedSubObject
始终

DeterminePostBackMode
始终

OnPreInit
始终

LoadPersonalizationData
始终

InitializeThemes
始终

OnInit
始终

ApplyControlSkin
始终

ApplyPersonalization
始终

OnInitComplete
始终

LoadPageStateFromPersistenceMedium
PostBack

LoadControlState
PostBack

LoadViewState
PostBack

ProcessPostData1
PostBack

OnPreLoad
始终

OnLoad
始终

ProcessPostData2
PostBack

RaiseChangedEvents
PostBack

RaisePostBackEvent
PostBack

OnLoadComplete
始终

OnPreRender
始终

OnPreRenderComplete
始终

SavePersonalizationData
始终

SaveControlState
始终

SaveViewState
始终

SavePageStateToPersistenceMedium
始终

Render
始终

OnUnload
始终


通过查看页面生命周期的低级别详细信息,我们可以发现在何处能够自然地实现 ASP.NET 2.0 中的许多功能,例如主题和个性化。例如,可以在 IntializeThemes 事件中处理一个主题,在 LoadPersonalizationData 中加载个性化数据,并在以后应用于 ApplyPersonalization 方法。请注意,对于决定 Web 应用程序的最终外观的 UI 元素而言,方法的顺序极其重要。

跨页发送
页面生命周期的其他主要变化包括事件和 Web 窗体的回发。在 ASP.NET 1.x 中,Web 窗体是自动回发给其宿主页面的。也就是说,当用户提交窗体时,窗体数据将始终被提交回包含原始窗体的页面。这种设计可以很容易地存储控件状态,但限制了开发人员执行更复杂操作的能力。

在 ASP.NET 2.0 中,Web 窗体控件有一个新属性,使开发人员可以决定执行提交操作时将窗体数据发送到何处。大多数情况下都需要使用回发机制,因此该机制仍然是默认设置。但是,如果开发人员希望将数据发送到不同的窗体,现在这是可以实现的。



图 4:回发和跨页发送

例如,您可以创建一个多页面向导,其中包含几个不同的窗体。每个窗体按顺序提交给下一个页面,直到用户到达可以执行最终验证的摘要页面。可以在当前上下文中通过 PreviousPage 对象访问上一个页面上的数据。PreviousPage 对象用于存储上一个页面上经验证、可供在当前页面上使用的数据。正是因为有了这个对象,跨页发送才不会牺牲控件的持久性。如果用户需要按顺序备份一个窗体,可以立即访问这些页面数据,而不必重新输入所有数据。

返回页首
可扩展性
ASP.NET 最初被设计成一个开放式框架。也就是说,构成 ASP.NET 的许多模块和组件都可以扩展、修改或替换以满足您的特定需要。在 ASP.NET 2.0 中,通过新的 HTTPHandlers 和 HTTPModules(现在是框架的标准组成部分)清楚地说明了框架的可扩展本质。

请求管道
在 ASP.NET 中,请求通过 Internet 服务器应用程序编程接口 (ISAPI) 筛选器从 Web 服务器传出,并传递给实际的 ASP.NET 运行库。



图 5:请求管道

当 IIS 接收到请求时,将根据 IIS 设置将扩展名映射到 ISAPI 筛选器。.aspx、.asmx、.asd 及其他扩展名被映射到 aspnet_isapi.dll,这只是一个用于启动 ASP.NET 运行库的 ISAPI 筛选器。当请求遇到 ASP.NET 运行库后,它将在 HTTPApplication 对象(作为 ASP.NET Web 应用程序的主机)上启动。HTTPApplication 对象:

1.
读取计算机和应用程序级别的配置文件。

2.
将请求传递给一个或多个 HTTPModule 实例。每个 HTTPModule 提供一项服务,例如会话维护、验证或配置文件维护。这些模块将请求传递回 HTTPApplication。

3.
基于动词和路径将请求传递给 HTTPHandler。动词是指请求中使用的 HTTP 动词(GET、POST、FTP 等),路径是指应用程序中的 URL。根据处理程序的配置方式,请求可能会被作为 ASP.NET 页面(System.Web.UI.Page 是 IHTTPHandler 的实现)进行处理,也可能会触发其他操作,例如对所有 Web 页面进行批编译(precomiplation.asd 将触发 PrecompHandler)。


在 ASP.NET 2.0 中,此模型没有任何变化,但添加了几个新的模块和处理程序以提供更多服务。就像在 ASP.NET 1.x 中一样,您可以扩展、替换或重新配置任何模块或处理程序类以提供自定义功能。

新模块
显然,新增的 HTTPModules 用于支持 ASP.NET 2.0 中提供的新服务。具体来说,具有默认模块设置的 ASP.NET 应用程序包括为以下目的而添加的新模块:

• SessionID - 会话标识机制已从 ASP.NET 1.x 会话模块中拆分开来,目的是为了更好地控制 Cookie、URL 重写以及生成会话 ID 的其他形式。

• 角色管理 - 这是一个新增的模块,用于提供基于角色的服务,以支持新的用户标识机制。此模块有助于将 ASP.NET 应用程序链接到 .NET Framework 中内置的、基于角色的安全性。

• 匿名标识 - 支持匿名用户的新的个性化功能。此模块可以帮助跟踪匿名用户可以访问的功能,以及跟踪在请求之间维护这些功能的方式。

• 配置文件 - 配置文件模块与新的配置文件服务相链接,可以帮助提供用户特定的持久性数据存储。

• 页面计数器- ASP.NET 2.0 中增加的一个新模块,用于链接页面计数器并提高 Web 通信的统计分析。


除了这些新模块以外,某些旧模块的行为也发生了变化:例如,输出缓存模块现在支持本白皮书后面介绍的新的缓存技术。

新处理程序
除了新模块以外,ASP.NET 2.0 还引入了新的处理程序,以支持应用程序配置工具和其他新功能,例如批编译请求。最重要的新处理程序包括用来处理 Web 站点管理请求的“.axd”系列。这些处理程序将启动允许开发人员配置 ASP.NET 用户和其他设置的内部管理工具。这些管理处理程序包括:

• Web 管理 - WebAdminHandler 是管理 Web 站点的主页。此处理程序为管理 ASP.NET 2.0 Web 应用程序提供了一个起点。

• 跟踪 - ASP.NET 1.x TraceHandler 已得到改进,是 ASP.NET 1.x 中唯一可用的“axd”处理程序。

• Web 资源 - 借助新的管理工具和 WebResourcesHandler,现在可以将 Web 资源配置为后部署。

• 缓存的图像 - CachedImageServiceHandler 支持缓存图形组件。

• 计数器 - SiteCountersHandler 使用页面计数器模块来提供 ASP.NET 2.0 应用程序的访问统计信息。

• 预编译 - 如前文所述,可以使用 PrecompHandler 对 ASP.NET 应用程序中的所有 ASPX 页面进行批编译。

• Web 部件导出 - WebPartExportHandler 支持存储和传输 Web 部件布局。Web 部件是一个新机制,用于个性化门户样式的 Web 应用程序的外观和内容。


与以前一样,HTTPForbiddenHandler 被链接到不应返回的任何文件类型。在 ASP.NET 2.0 中,被禁止的文件类型列表已被扩展,现在包括母版页、外观文件及其他新的开发人员组件。

返回页首
高级缓存技术
提高 Web 应用程序性能的一种方法是在内存中缓存静态内容。返回缓存的内容始终要比返回新渲染的内容快。但是,缺点是缓存的内容可能会过期。ASP.NET 1.x 支持几种缓存,包括:

• 页面级别 - 每个页面可以作为一个整体进行缓存,或基于用来访问该页面的参数进行缓存。缓存的页面将在指定时间后过期。

• 页面片段 - 如果使用用户控件(.ascx 文件)构建页面,则可以独立于页面内容的其余部分单独缓存用户控件。

• 编程缓存 - 由于有了缓存 API,开发人员还可以缓存对象。缓存 API 具有一个显著的优点,它使开发人员可以为何时应刷新缓存创建不同类型的依赖关系。


在 ASP.NET 2.0 中,页面级别的缓存机制已得到扩展,以支持数据库依赖关系。借助数据库缓存依赖关系,可以将缓存的页面绑定到 SQL Server 数据库中的特定表。如果表发生变化,缓存将自动过期。此外,开发人员现在可以使用缓存后替换功能,用刷新的内容替换缓存的部分内容。缓存后替换功能允许应用程序使用页面级别的缓存,即使页面的部分内容应动态生成。

数据库缓存过期
对大多数数据驱动的 Web 站点来说,缓存是一个复杂的主题,特别是在需要缓存且必须更新数据的情况下。在 ASP.NET 1.x 中,页面可以缓存一段时间,并通过输入参数(查询字符串或 POST 参数)进行组织:

列表 5:ASP.NET 1.x 输出缓存指令

<%@ outputcache duration="3600" varybyparam="ProdID" %>

例如,列表 5 中的代码基于变量 ProdID 在内存中将页面缓存一小时。以上示例中出现的问题是,如果相关业务数据在其他地方被更新,应该怎么办?例如,假设按照产品 ID 缓存一个产品目录页面。如果从管理站点更新了此产品的相关信息(例如库存数量或价格),那么缓存的数据将不正确,显示给客户的数据也不正确。在以前版本的 ASP.NET 中,要解决此问题,需要使用 Response.RemoveOutputCacheItem 手动从缓存中删除该页面,或等到 duration 时间过期后让系统自动更新页面。

ASP.NET 2.0 通过支持数据库缓存依赖关系,从而解决了这一问题。使用 SQL Server 7 和 200 时可以使用表级别的通知,并且 Microsoft SQL Server 2005 将提供更精确级别的通知。例如,以下代码最多可以将产品页面缓存一小时,但添加了对数据库表的第二层依赖关系。

列表 6:ASP.NET 2.0 数据库缓存示例

<%@ outputcache duration="3600"
varybyparam="ProdID"
sqldependency="MyDatabase:Products" %>

使用新的 sqldependency 属性时,只要对“Products”表进行更改,缓存的页面就将过期。sqldependency 属性必须引用在 web.config 文件中配置的 datasource。datasource 用于标识数据库连接以及发出依赖关系通知所必需的参数。

自定义缓存依赖关系
ASP.NET 2.0 附带了一个 CacheDependency 实现,即 SQLCacheDependency 类,它支持 Microsoft SQL Server。实现新的缓存依赖关系是一个复杂的过程,但由于 ASP.NET 2.0 具有可扩展性,因此这一过程是可以实现的。换句话说,您可以创建自己的 CacheDependency 类,以便为其他数据库系统(如 Oracle 或 Sybase)提供类似的功能。

缓存后替换功能
对于几个页面元素保持动态更新,而页面的大部分内容适于缓存的情况,可以利用 ASP.NET 2.0 提供的缓存后替换功能。缓存后替换功能用于通知 ASP.NET 运行库是否应在向用户显示缓存的页面之前重新评估该页面上的某个特定元素。

使用此功能的方法有两种:

• 调用新的 Response.writeSubstitution 方法,以传递对替换回调函数的引用。

• 将 <asp:substitution> 控件添加到 Web 页中,并将 methodname 属性设置为回调函数的名称。

• 不管采用哪种方法,都需要在页面中添加用来指定依赖关系的持续时间和位置的 @OutputCache 指令。


实现缓存后替换功能
可以创建支持缓存后替换功能的控件,以充分利用此功能。这类控件的一个示例就是 AdRotator。列表 7 展示了这样一个页面:

• 从“Pubs”数据库的“authors”表中检索数据。

• 将数据绑定到 GridView 控件。

• 显示来自 AdRotator 的广告。

• 在标签控件上显示创建页面的时间。


该示例中还添加了 <asp:substitution> 控件(列表中以粗体显示的行)。此控件将其 methodname 属性设置为 uncachedUpdate(返回字符串输出的方法,本例中为当前时间)。无论缓存了什么内容,替换控件都将返回正确的时间。

列表 7:PostCache.ASPX 源代码

<%@ Page language="c#" Codebehind="PostCache.ASPX.cs"
AutoEventWireup="true" Inherits="WebApplication1.PostCache" %>
<%@ outputcache duration="30" varybyparam="none" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<DIV style="DISPLAY:inline;
Z-INDEX:101; LEFT:32px; WIDTH:160px;
POSITION:absolute; TOP:24px; HEIGHT:8px"
align="right" ms_positioning="FlowLayout">
this page was created at:
</DIV>
<asp:Label id="CreatedTime"
style="Z-INDEX:102; LEFT:200px; POSITION:absolute;
TOP:24px" runat="server" Width="120px" Height="16px">
</asp:Label>
<asp:substitution id="UpdatedTime" methodname="uncachedUpdate"
style="Z-INDEX:103; LEFT:200px; POSITION:absolute;
TOP:48px" runat="server" Width="112px" Height="11px">
</asp:substitution>
<DIV style="DISPLAY:inline; Z-INDEX:104; LEFT:32px;
WIDTH:160px; POSITION:absolute; TOP:48px;
HEIGHT:16px" align="right" ms_positioning="FlowLayout">
and last updated at:
</DIV>
<asp:AdRotator id="Ads" style="Z-INDEX:105; LEFT:312px;
POSITION:absolute; TOP:16px" runat="server"
Width="80px" Height="60px" AdvertisementFile="img/Ads.xml">
</asp:AdRotator>
</form>
</body>
</HTML>

此页面的内含代码文件包含支持 uncachedUpdate 方法的缓存后替换功能所必需的事件。请注意,Page_Load 方法将报告加载页面的时间,因此我们可以确定缓存发生的时间。

列表 8:PostCache.ASPX.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace WebApplication1 {
public class PostCache :System.Web.UI.Page {
protected System.Web.UI.WebControls.Label CreatedTime;
protected System.Web.UI.WebControls.Label UpdatedTime;
protected System.Web.UI.WebControls.AdRotator Ads;

private void InitializeComponent() {
this.Load += new System.EventHandler(this.Page_Load);
}

private void Page_Load(object sender, System.EventArgs e) {
CreatedTime.Text = DateTime.Now.ToShortTimeString();
}

protected String uncachedUpdate() {
return DateTime.Now.ToShortTimeString();
}
}
}

当前使用的缓存后替换功能
图 6 显示了 PostCache 页面的输出。首次运行该应用程序时,我们可以看到“page created”和“last updated”时间是相同的。



图 6:PostCache.ASPX 的输出

以后调用同一页面时,我们将看到使用缓存后替换功能的效果。尽管创建页面的时间和图像仍然相同,但上次更新时间是不同的。



图 7:第二次请求时的 PostCache 输出

由于使用了缓存指令,因此创建时间和 adRotator 图像仍然保持不变。该页面将缓存 30 秒。30 秒过后,创建时间和 adRotator 都将在下次请求时更新。但是,<asp:substitution> 控件(调用 uncachedUpdate() 方法)将在每次请求页面时更新,而不管其缓存状态。

通过正确使用缓存后替换功能,开发人员只需更新页面上的动态内容,从而可以显著提高其 Web 应用程序的性能。将使用 ASP.NET 2.0 开发的 Web 应用程序与数据库缓存过期和异步页面更新相集成,可以消除传统的 Web 请求和响应体系结构暴露出来的许多局限性。

返回页首
性能
尽管 ASP.NET 2.0 中的基础结构发生了变化而且增加了一些功能,但仍然存在一个问题,那就是 ASP.NET 2.0 的执行速度有多快?因为 ASP.NET 2.0 仍在开发过程中,所以性能方面的数据还无法获得。尽管如此,我们已经付出了很大的努力,以确保 ASP.NET 2.0 框架能够在各方面保持现有的性能或有所改进。

改进的请求管道
所有开发人员都能看到请求管道的性能有所改进。尽管添加了许多新的事件连接,但基本的 ASP.NET 请求堆栈速度要比在 ASP.NET 1.1 中快出高达 30%。您可以通过创建显示“Hello World”的简单页面来感受一下改进的性能。因为该页面不包含高级功能,所以您可以直接测试 HTTPHandler 和 HTTPModule 管道以及连接 ASP.NET 2.0 和 IIS 的 ISAPI 插件。不管使用哪个版本的 IIS,您都能看到性能方面的改进,因为此代码已被优化以加快处理速度。

使用 IIS 6.0 改进的内存管理
ASP.NET 2.0 中的某些性能改进完全是因为使用了 IIS 6.0。例如,如果使用 IIS 6.0,在使用 100 个并发用户请求一个包含几个控件的页面的加载测试中,辅助进程的工作集减少了大约 50%。这意味着,对于给定服务器,操作系统使用的资源大约是以前必须使用的资源的一半。

在专门模拟中等复杂程度的 ASP.NET 页面的测试中,与在 IIS 5.0 上运行的同一页面相比,系统负载(内存和 CPU 使用量)已显著降低。这种特定的性能改进是通过将响应缓冲区从托管内存移至本机内存来完成的。由于不再需要将托管内存固定到特定的响应,因此 ASP.NET 2.0 不仅消除了资源瓶颈,还能够更快地响应每个请求。

其他的性能改进是通过紧密集成 IIS 6.0 与 Windows 操作系统内核而实现的。IIS 6.0 在内核级别执行某些缓存和缓冲操作,从而提高了所有 Web 应用程序(包括 ASP.NET)的性能。

其他改进
作为一名开发人员,您肯定希望 ASP.NET 2.0 与 ASP.NET 1.x 的操作速度相同或更快。现在,核心功能已经具备,您一定能够从最终版本的 ASP.NET 2.0 中看到所期待的其他性能改进。

返回页首
结论
为了提高开发人员的工作效率,ASP.NET 2.0 进行了许多体系结构方面的改进。不仅改进了代码模型以减少冲突,还扩展了编译进程,从而为编译和部署 Web 应用程序提供了多种选择。ASP.NET 框架的可扩展性再次通过新的 HTTPModules 和 HTTPHandlers(支持 ASP.NET 中的许多新功能,包括个性化、母版页和管理站点)显示出来。缓存功能得到改进,允许使用数据库依赖关系和缓存后替换功能。从内部来看,ASP.NET 2.0 与以前的版本相比具有很大的改进;新的实现并入了开发人员驱动的许多改进功能,同时沿用了行业最佳做法。ASP.NET 2.0 提供了世界一流的 Web 开发平台,可以处理复杂的企业 Web 应用程序开发。

参考资料

• CodeNotes for ASP.NET

• A First Look at ASP.NET V. 2.0

• ASP.NET 2.0 Revealed


关于作者

Jayesh Patel - Jay Patel 是擅长 .NET 和 Java 技术的开发人员。Jay 主要研究基于模式的编程和灵活的方法。

Bryan Acker - Bryan Acker 是 Infusion Development 的技术撰稿人。Bryan 在 ASP 和 ASP.NET Web 开发及 Web 托管技术方面有着很强的专业背景。

Robert McGovern - Rob McGovern 是 Infusion Development 的高级撰稿人、开发人员兼项目经理。Rob 曾参与几个不同的 ASP.NET 项目,包括“CodeNotes for ASP.NET”和“JSP to ASP.NET migration guide”。

Infusion Development Corporation 是通过 Microsoft 认证的解决方案提供商,为世界 1000 强企业提供量身定做的软件开发、培训和咨询服务,主要服务于金融服务行业。Infusion Development 在纽约和多伦多设有办事处,已建立一定的国际客户基础,包括金融服务、证券经纪和软件开发行业中的一些世界最大的公司。Infusion Development 的员工也是 CodeNotes 系列丛书的作者和创始人。