UML软件工程组织

ASP.NET 2.0 的内部变化
来源:www.microsoft.com 作者:Jayesh Patel

摘要:虽然 ASP.NET 2.0 百分之百地向后与 ASP.NET 1.1 兼容,但它给 ASP.NET 带来了很多内部的变化。包括针对代码模型、编译、页面生命周期的变化等等。本文将概述这些变化。

本页内容
简介 简介
代码模型 代码模型
编译 编译
完整的运行时编译(\app_code 目录) 完整的运行时编译(\app_code 目录)
页面生命周期 页面生命周期
可扩展性 可扩展性
高级缓存技术 高级缓存技术
性能 性能
小结 小结

简介

对 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 技术资源库文章,Web Forms Code Model。

继承的复杂性

ASP.NET 的设计范例是,开发人员将使用 Microsoft Visual Studio .NET 将控件拖放到 ASPX 页面。然后,Visual Studio 将自动在代码隐藏文件中生成适当的支持代码。如果将控件添加到 ASPX 页,就必须将新代码添加到代码隐藏文件。换句话说,尽管这种继承关系强调其他的方式,但实际上是 ASPX 页驱动代码隐藏文件的设计。

编译的复杂性

第二个同步问题是文件的编译方式。所有的代码隐藏文件,连同支持类,都编译到一个程序集并存储在 Web 应用程序的 /bin 目录中。在部署应用程序之前进行编译步骤。另一方面,ASPX 页在第一次请求该页的运行时进行编译。ASP.NET 运行库实际上将 ASPX 页编译到它自己的临时程序集中。

这个过程的问题是,在没有更新代码隐藏程序集的情况下可以更改 ASPX 页。也就是说,部署之后开发人员可能选择修改 ASPX 页的一个属性或者更改控件的类型,但既没有更新代码隐藏文件,也没有重新编译应用程序程序集。当发生这种情况时,因为代码隐藏文件和它们相关的 ASPX 页不匹配,所以应用程序将会遇到不可预料的错误。

ASP.NET 2.0 中的编码模型

ASP.NET 2.0 继续提供代码内联和代码隐藏编码模型。就代码内联模型而言,除了 Microsoft Visual Studio 支持单文件开发之外,几乎没有变化。有关 Visual Studio 中的变化以及它如何处理代码内联的详细信息,请参阅本文

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 2.0 中同样的 ASP.NET 页生成的代码隐藏文件更为整洁。

清单 2. ASP.NET 2.0 中的代码隐藏文件

namespace ASP{ 
public partial class MyPage : System.Web.UI.Page 
{ 
} 
} 

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

清单 3. 在新代码隐藏文件中添加事件

namespace ASP{ 
public partial class Webform1_ASPX: System.Web.UI.Page 
{ 
void Page_Load(object sender, EventArgs e) 
{ 
  Label1.Text = "Hello ASP.NET.0"; 
} 
} 
} 

事件语法可以通过 Visual Studio 生成。获得的代码隐藏文件更为简短并且不受自动生成代码的影响。ASP.NET 运行库自动将代码隐藏中的事件连接到 ASPX 中的控件。换句话说,ASP.NET 运行库现在自动执行代码生成,而过去这由 Visual Studio 完成。

两步编译新方法的好处在于,它允许您将代码隐藏类编译成二进制,然后支持部署 .ASPX 文件,该文件作为以后修改的 html 源。您不能部署已编译的代码隐藏,以及稍后修改 .ASPX html 源,因为 .ASPX 源是编译代码隐藏所用的局部类型。

继承的复杂性

这种新代码隐藏模型大大降低了继承的复杂性。因为 ASPX 页不直接继承代码隐藏文件,所以代码隐藏文件不再需要定义和支持 ASPX 页上定义的所有控件。同样,代码隐藏文件可以自动访问 ASPX 页上的任何控件,而不需要 ASP.NET 1.x 中所需的声明代码。所有这些都是可能的,因为 ASP.NET 运行库自动将所需要的声明和事件连接代码插入到最终的已编译文件中。因为运行时承担这样的责任,所以代码开发人员和 Web 开发人员都不需要为此担心。

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

编译的复杂性

因为这种新的代码隐藏文件与 ASPX 页连接并在运行时编译成一个完整类,所以不会出现编译的复杂性。也就是说,代码隐藏文件自动与 ASPX 页同步。即使是使用这种新编译模型,仍可能有不同步的代码,但可以很快找到这种问题,因为产生的异常更为清楚。

编译

由于 ASP.NET 1.x 中引入了页模型,ASP.NET Web 页的编译过程总是分成两个阶段。首先,代码隐藏文件和其他支持类编译到一个程序集中,然后在运行时编译单独的 ASPX 文件。虽然这种模型具有许多优点,但它也有一些缺点。ASP.NET 2.0 提供基本模型的几种替代模型,并根据您的特定需要提供更为广泛的编译选项。

ASP.NET 1.x中的编译

ASP.NET1.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 中,可以通过修改 <compilation> 标记在 web.config 文件中配置批编译。

清单 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 的一种新功能,允许在部署前对项目进行完整编译。在完整编译中,所有的代码隐藏文件、ASPX 页面、HTML、图形资源以及其他的后端代码都被编译到一个或多个可执行程序集中,这取决于应用程序的大小和编译设置。这些程序集包含所有的已编译 Web 站点代码,而资源文件和配置文件被复制,没有做修改。这种编译方法以牺牲修改部署后 Web 站点的能力为代价,提供了最好的性能和安全性。如果您使用高可见或高安全的 Web 站点,这种选项是最终部署的最好选择。但是,如果您正在构建一个运行局部 Intranet 的小站点,并且更改站点非常频繁,那么完整预编译可能有点过分。

ASP.NET 2.0 编译模型也允许预编译应用程序的所有代码隐藏文件并且仍可以更新代码。可以将代码隐藏文件和原始的 .ASPX 文件(都是局部类)编译到一个预编译类中(页面的基类)。如果选择在运行时编辑 .ASPX 文件,只需重新编译页面即可。

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

选择最佳的编译选项要由具体的情况和需要决定,但编译模型要有灵活性。即使选择使用 \app_code 目录来存储代码隐藏文件,您仍可以使用完整的编译方法来部署应用程序。

批编译

在 ASP.NET 2.0 中,可以利用单个 URL 请求来批编译任何应用程序。如同 ASP.NET 1.x 一样,批编译消除了第一次页面请求的延时,但造成了更长的启动周期。另外,批编译还要求在部署前编译代码隐藏文件。

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 服务器上部署的原始代码数量,并提供了最佳的安全性。这个增加的进程可以内置于通常的开发/测试/部署周期中,而工作效率并不会有多大损失。

完整的运行时编译(\app_code 目录)

在目前描述的所有三种编译方法中,在部署前必须要编译所有的代码文件(代码隐藏类和支持类)。在 ASP.NET 2.0 中,您有代码目录。

\app_code 目录是一个保存未编译类的特殊目录。在运行时,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 显示综合方法列表。Method 列显示实际事件方法名称,Active 列表示该事件是否总处于活动状态或者仅在 PostBack 操作期间是活动的。例如,新方法 TestDeviceFilter 可用来确定哪种设备筛选器就绪,并用这些信息来决定如何显示页面。另一方面,新方法 LoadControlState 只有在 PostBack 期间才激发。可以重写该方法(结合 SaveControlState)来在 PostBack 期间创建保存和还原控件状态的备选序列化方案。

表 1. 页面生命周期方法
方法 活动

Constructor

Always

Construct

Always

TestDeviceFilter

Always

AddParsedSubObject

Always

DeterminePostBackMode

Always

OnPreInit

Always

LoadPersonalizationData

Always

InitializeThemes

Always

OnInit

Always

ApplyControlSkin

Always

ApplyPersonalization

Always

OnInitComplete

Always

LoadPageStateFromPersistenceMedium

Always

LoadControlState

Always

LoadViewState

Always

ProcessPostData1

Always

OnPreLoad

Always

OnLoad

Always

ProcessPostData2

Always

RaiseChangedEvents

Always

RaisePostBackEvent

Always

OnLoadComplete

Always

OnPreRender

Always

OnPreRenderComplete

Always

SavePersonalizationData

Always

SaveControlState

Always

SaveViewState

Always

SavePageStateToPersistenceMedium

Always

Render

Always

OnUnload

Always

查看页面生命周期的底层细节,我们可以看到 ASP.NET 2.0 中提供的许多功能(例如主题和个性化)将在什么地方容易实现。例如,主题在 IntializeThemes 事件中处理,而个性化数据将在 LoadPersonalizationData 中加载并稍后用于 ApplyPersonalization 方法。请注意,就哪一个 UI 元素将决定 Web 应用程序的最终外观和感觉而言,方法的顺序非常重要。

跨页投递

页面生命周期中的另一个主要变化涉及回发事件和 Web 窗体。在 ASP.NET 1.x 中,Web 窗体自动回发给它们的宿主页。即,当用户提交一个窗体时,窗体数据总是提交回包含该原始窗体的页面。这种设计决策考虑了存储控件状态的容易性,但限制了开发人员执行更复杂操作的能力。

在 ASP.NET 2.0 中,Web 窗体控件有一个新特性,让开发人员决定在进行提交操作时将窗体数据发送到何处。在大多数情况下,要求 PostBack 机制,因此它仍是默认的机制。但是,如果开发人员想将数据投递到不同的窗体,现在是可能的。


图 4. PostBack 与跨页投递

例如,可以创建一个包含几个不同窗体的多页向导。每个窗体依次提交给下个页面,直到用户到达一个进行最后验证的摘要页。通过 PreviousPage 对象可以在当前的上下文中访问来自最后一页的数据。PreviousPage 对象存储来自前页的已验证数据,以在当前页中使用。由于这个对象,跨页投递不会牺牲控件的持久性存储。如果用户需要依次倒退回一个窗体,那么就可以立即访问该页的数据,而用户不必重新输入所有的数据。

可扩展性

ASP.NET 最初被设计为一种开放式框架。即,可以扩展、修改或替换构成 ASP.NET 的许多模块和组件,以适应特定的要求。在 ASP.NET 2.0 中,这种框架的可扩展特性由新的 HTTPHandlersHTTPModules 清楚地阐明,二者现在是该框架的一个标准部分。

请求管道

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


图 5. 请求管道

当 IIS 接收一个请求时,根据 IIS 的设置将扩展映射到一个 ISAPI 筛选器。将 .ASPX、.asmx、.asd 和其他扩展映射到 ASPnet_isapi.dll,该 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.PageIHTTPHandler 的一种实现)加以处理,或者该请求可能触发另一个操作,例如批编译所有的 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 框架中基于角色的安全性结合起来。

匿名识别 — 新的个性化功能支持匿名用户。该模块有助于跟踪匿名用户可以访问的功能,以及跟踪在请求之间维护这些功能的方式。

配置文件 — 该配置文件模块连接新的配置文件服务,帮助为用户提供特定的持久数据存储。

除了这些新模块,一些旧模块的行为也进行了更改:例如,输出缓存模块现在支持新的缓存技术,这将在本白皮书的稍后部分说明。

新处理程序

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

Web 管理 — WebAdminHandler 是管理 Web 站点的主页。该处理程序为管理 ASP.NET 2.0 Web 应用程序提供了起点。

跟踪 — ASP.NET 1.xTraceHandler 已进行了改进,它是 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 和 2000 时可用表级通知,而 Microsoft SQL Server 将以更多的粒度级别来提供通知。例如,下列代码缓存一个产品页最多 1 个小时,但添加了第二个数据库表依赖项。

清单 6. ASP.NET 2.0 数据库缓存示例

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

利用 sqldependency 新属性,如果对 Products 表有任何更改,那么缓存页将到期。sqldependency 属性必须引用一个 datasource,它在 web.config 文件中进行配置。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,将一个引用传递给替代回调函数。

给 Web 页添加一个<asp:substitution> 控件并将 methodname 属性设置为回调函数的名称。

对于任一种选项,应当给页面添加一个 @OutputCache 指令,指定依赖项的持续时间和位置。

实现缓存后替代

为了利用这种功能,可以创建能够识别缓存后替代的控件。AdRotator 控件就是一个这样的控件示例。清单 7 举例说明了这样一个页:

检索 Pubs 数据库的作者表数据。

将数据绑定到 GridView 控件。

从一个 AdRotator 显示地址。

在一个标签控件中显示创建该页的时间。

该示例中还添加了一个 <asp:substitution>控件(清单中的粗体行)。该控件的 methodname 属性设置为 uncachedUpdate(一种返回字符串输出的方法 — 在这种情况下为当前时间)。无论缓存什么内容,替代控件都将返回正确的时间。

清单 7. PostCache.ASPX 源代码

<%@ Page language="C#" Codefile="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 页的输出结果。首次运行应用程序,我们可以看到“创建页”时间和“最后更新”时间是相同的。


图 6. PostCache.ASPX 的输出结果

连续调用同一个页面,我们可以看到缓存后替代的效果。虽然页面创建时间和图像保持不变,但最后更新时间有变化。


图 7. 第二次请求的缓存后输出结果

由于缓存指令,创建时间和 adRotator 图像都保持不变。该页缓存了 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 中的速度快。通过创建一个显示“Hello World.”的简单页,可以评估提高的性能。因为该页没有高级功能,所以直接测试的是 HTTPHandlerHTTPModule 管道,以及将 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 框架的扩展性再次通过新的 HTTPModulesHTTPHandlers 显示,它们支持建立在 ASP.NET 中的许多新功能,包括个性化、母版页和管理站点。缓存已经改进以允许数据库依赖项和缓存后替代。从内部来看,ASP.NET 2.0 包含了对它以前版本的显著改进;这些新实现结合了许多开发人员驱动的实现,同时沿用了业界的最佳做法。ASP.NET 2.0 提供一种世界一流的 Web 开发平台,该平台是为处理复杂的企业 Web 应用程序开发而构建的。

 

 

版权所有:UML软件工程组织