Temporary ASP.NET Files 文件夹中保存的是什么内容

作为一名 ASP.NET 开发人员,您可能非常清楚 ASP.NET 如何处理 .aspx 资源中的代码,如何对标记进行分析并将其动态转换成 Visual Basic® 或 C# 类,等等。但是接下来呢?ASP.NET 生成的文件保存在哪里?如何利用它们满足页面请求?从上个月起,我开始关注这一过程。在本月的内容中,我将对服务器上所发生的操作进行分析,以便您能够避免某些常见的问题隐患。 我将讨论 ASP.NET 临时文件的存储以及动态生成的用于为页面响应提供服务的类的源代码。此外,我还将构建一个可以与任何 ASP.NET 2.0 或 ASP.NET AJAX(原代号为“Atlas”)应用程序一同使用的资源管理器工具,以查看和调试您的页面执行的实际代码。但在此之前,您需要了解几个事项。(和上月专栏一样,本部分内容主要以那些没有文档记录的 ASP.NET 工作原理细节为基础来展开。这些实施细节在未来的 Microsoft® .NET Framework 版本中可能会发生变化。)


Temporary ASP.NET Files 文件夹中保存的是什么内容?

ASP.NET 页面请求的处理过程需要使用一些临时文件。当您在 Web 服务器上安装 ASP.NET 2.0 时,所创建的文件夹层次结构如下:

%WINDOWS%\Microsoft.NET\Framework\v2.0.50727

 

这里的版本号指的是 ASP.NET 2.0 的零售版。ASP.NET 的每个发布版本(包括每个过渡性的内部版本)都有一个唯一的版本号,并且会创建不同的文件夹树,以便支持不同版本的并行执行。因此,您务必要指定您的应用程序所适用的 ASP.NET 版本,这一点极为重要。在 ASP.NET 1.x 和 ASP.NET 2.0 下运行的应用程序基于物理形式上独立的文件夹。在 Microsoft.NET\Framework 文件夹下,您会找到与已安装的 ASP.NET 版本数相同数量的 vX.X.XXXX 子文件夹(请参见图 1)。

图 1 ASP.NET 1.0、1.1、2.0 和 3.0 运行库文件
图 1 ASP.NET 1.0、1.1、2.0 和 3.0 运行库文件 (单击该图像获得较大视图)

在已安装版本的根文件夹下,您会看到许多子目录。CONFIG 文件夹包含计算机配置文件,其中包括 machine.config 和用于所有站点的基本 web.config 文件。名为 ASP.NETWebAdminFiles 的文件夹包含构成网站管理工具的源文件,您可从 Visual Studio® 2005 内部运行该工具。最后,Temporary ASP.NET Files 文件夹包含为页面和资源提供服务而创建的所有临时文件和程序集。要找到为您的 Web 页面动态创建的文件,您需要查看此文件夹子树。请注意,Temporary ASP.NET Files 目录是存放动态创建的文件的默认位置,但可以使用 web.config 文件中的 <compilation> 部分按应用程序对其进行配置:

<compilation tempDirectory="d:\MyTempFiles" />

 

当应用程序第一次在计算机上执行时,在临时文件目录下就会创建一个新的子文件夹。编译子文件夹的名称与应用程序的 IIS 虚拟目录的名称相同。如果您只是使用 Visual Studio 2005 内嵌的 Web 服务器测试应用程序,那么子文件夹会采用该 Web 应用程序的根文件夹的名称。如果您从 Web 服务器的根文件夹调用页面,您将在根子文件夹下找到它们的临时文件(请参见图 2)。

图 2 Web 测试服务器上的 WebApp
图 2 Web 测试服务器上的 WebApp (单击该图像获得较大视图)

在应用程序的编译子文件夹下,有一组使用散列名称的目录。此处显示了通常可以找到临时文件的路径。(最后两个目录包含的是假名称,但实际显示的就是这样的名称。)

\v2.0.50727\Temporary ASP.NET Files\MyWebApp\3678b103\e60405c7

 

您可以使用以下语句,以编程的方式检索指定应用程序的临时文件所在的子文件夹的路径:

Dim tempFilesFolder As String = HttpRuntime.CodegenDir

 

ASP.NET 会定期在应用程序发生改变、需要重新编译时清理编译文件夹并删除陈旧的资源,但 Temporary ASP.NET Files 目录下的子树的大小可能会显著地增加,在测试计算机上更是如此。 作为管理员,您应密切关注 Temporary ASP.NET Files 下的目录,并确保所有目录都是与当前活动的应用程序有关的。如果您无意间删除了一个处于活动状态的应用程序的子树,不必惊慌。您将丢失所有预编译的页面和资源并会将应用程序重置到其最初的编译状态;但下一个请求将触发对每个页面或一批页面(具体取决于配置)执行新的编译过程,因此最终不会丢失任何信息或页面,只不过用户在处理下一个请求时将感觉到首次命中延迟。现在,我们来看某一应用程序的编译文件夹的内容。

Back to top


保留文件

对于应用程序中的每个页面,页面编译进程会生成一个下述名称的文件:

[page].aspx.[folder-hash].compiled

 

[page] 占位符代表 .aspx 资源的名称。[folder-hash] 占位符是一个散列值,它使文件名保持唯一,避免与原本属于其他文件夹的同名文件混淆。这种文件称为保留文件,因为它们包含有重要的信息,这些信息可帮助 ASP.NET 运行库快速检索程序集以及检索将用于为页面请求提供服务的 HTTP 处理程序的类型名称。此外,保留文件还包含一个文件散列值,用于检测自从上次访问后文件的内容是否发生了改变。

构成某一应用程序的所有 .aspx 页面在同一个临时文件夹中进行编译,即使它们名称相同且位于不同的文件夹中也是如此处理。这一点如何实现?假设您的应用程序包含两个名为 test.aspx 的页面,位于不同的文件夹 - Folder1 和 Folder2 中。两个页面将在同一临时文件夹中进行编译,但可以通过它们的散列值对其进行区分,由于散列值是根据路径信息而不只是文件名计算出来的,因此它们的散列值是不同的。因而最终,两个 test.aspx 页面的保留文件名只在文件夹散列值部分有所不同:

Test.aspx.cdcab7d2.compiled
Test.aspx.9d86a5d7.compiled

 

散列值的内部存储缓存使 ASP.NET 运行库可以识别任何指定页面 URL 的散列值并快速找到相应的保留文件。如果没有找到保留文件,ASP.NET 会动态编译页面。当您部署没有预编译的应用程序时就会发生这种情况。另一方面,当您对一个站点进行预编译时,每个组成页面的保留文件被创建并放置在 Bin 文件夹中。

保留文件为纯 XML 文件。图 3 显示了一个示例保留文件的内容。

图 4 具体列出了文件的属性。<fileDeps> 部分列出了当前页面所依赖的文件。对任何依存关系所做的任何改动都将导致页面重新编译。FileHash 值代表依存关系状态的快照,而 Hash 代表当前页面文件状态的快照。值得注意的是,当您停止或重新启动 Web 应用程序时,完全基于文件更改通知来检测文件动态更改的机制会失败。按照散列值保存页面和依存关系的状态,使您可以随时检测到更改。

类型 (Type) 属性设置动态创建的类(将用于为请求提供服务)的名称。默认情况下,类型名称是 ASP.[page]_aspx,其中 [page] 代表页面文件的名称。但是请注意,您可以通过设置您的 .aspx 文件的 @Page 指令中的 ClassName 属性来更改此名称。根命名空间不会更改,因此类型名称可以是 ASP.[ClassName]。

程序集 (Assembly) 属性指示动态创建的程序集的名称,该程序集包含用于为请求提供服务的页面类。此类程序集的名称和内容取决于 web.config 文件的 <compilation> 部分中的设置。

默认情况下,应用程序页面以批处理模式编译,这意味着 ASP.NET 会尝试在一个程序集中容纳尽可能多的未编译页面。使用 maxBatchSize 和 maxBatchGeneratedFileSize 属性可以限制一个程序集中封装的页面数量以及程序集的总大小。默认情况下,每个批处理编译将拥有不超过 1000 个页面,并且所有程序集都不大于 1MB。一般来说,当第一次编译大量页面时,您不应让用户等待太长时间。同时,您不应该在内存中加载大型程序集而只是为一个小页面来提供服务,或者为每个页面启动编译。 maxBatchSize 和 maxBatchGeneratedFileSize 属性可帮助您在首次命中延迟和内存使用之间找到良好的平衡。 如果您选择站点预编译(请参阅本杂志 2006 年 1 月 Fritz Onion 的 Extreme ASP.NET 专栏),那么您不必担心首次命中延迟,但您仍应考虑最佳的批处理参数,以避免 Web 服务器的内存过载。

当批处理开启时,应用程序中的前 1000 个页面(实际数量取决于 maxBatchSize)被编译为名为 App_Web_[random] 的程序集,其中 [random] 是由八个字符组成的随机序列。 如果关闭批处理,则每个页面将产生各自的程序集。程序集的名称如下:

App_Web_[page].aspx.[folder-hash].[random].dll

 

要关闭批处理,可向 web.config 文件添加以下内容:

<compilation batch="false" />

 

如果您对一个示例应用程序的编译文件夹进行查看,您会找到名称中包含 CBMResult 的附带保留文件,还有一个具有相同名称的 .ccu 文件,如下所示:

test.aspx.cdcab7d2.compiled
test.aspx.cdcab7d2_CBMResult.ccu
test.aspx.cdcab7d2_CBMResult.compiled

 

列表中的第一个文件是保留文件。那么其他两个作何用途? CCU 代表代码编译单元 (Code Compile Unit),是指用于生成动态页面类的源代码而创建的 CodeDOM 树。 CCU 文件是二进制文件,包含经序列化的页面 CodeDOM 树。CBMResult 文件是保留文件,用于检查 CCU 是否最新、其所在的位置以及它基于哪些文件。

CBMResult 文件由与 ClientBuildManager 类通信的模块(例如,Visual Studio 2005 设计器和 IntelliSense®)来使用。这些模块查询页面的结构来获取语句结束信息。CCU 文件会保留准备为这些请求提供服务的页面的最新 CodeDOM 结构副本。

Back to top


页面类动态源代码

正如上面提到的,.aspx 资源被解析为 Visual Basic 或 C# 类。该类继承自 System.Web.UI.Page,或者很可能继承自某个从 System.Web.UI.Page 继承而来的类。事实上,在大多数常见情形下,动态页面类具有以下原型:

Namespace ASP
Public Class test_aspx
Inherits Test : Implements System.Web.IHttpHandler
...
End Class
End Namespace

 

在此例中,Test 类在页面的代码文件类中定义,它包括您在页面的附带类文件中写入的任何事件处理程序和帮助器例程。在您使用 Visual Studio 2005 时可能已经注意到,此代码文件类缺少页面成员的定义。对于您在 .aspx 源文件中找到的每个 runat=server 标记,在代码文件中应定义有相应类型的成员。ASP.NET 运行库系统会生成 Test 分部类,包含所有这些成员以及两个额外的属性 - Profile 和 ApplicationInstance。图 5 显示了参与为某一 .aspx 资源的请求提供服务的类集。

图 5 中的类跨两个不同的源文件。第一个包含分部类,用于完善代码文件中的类和由此派生出的用于为请求提供服务的实际页面类。第二个文件是您在项目中创建的代码文件的副本。这些文件根据程序集名称而命名。名称的结构如下:[assembly].X.vb。(如果您使用 C#,则为 .cs)X 为从 0 开始的递增索引值,可确保文件名唯一。

如果您查看示例 test.aspx 页面的编译文件夹的内容,您会发现创建了第三个文件,如下例中所示:

Namespace __ASP
Friend Class FastObjectFactory_app_web_test_aspx_cdcab7d2_xg83msu0
Private Sub New()
MyBase.New
End Sub
Shared Function Create_ASP_test_aspx() As Object
Return New ASP.test_aspx
End Function
End Class
End Namespace

 

类名称是以字符串 FastObjectFactory 为前缀的页面程序集的名称。该类具有一个名为 Create_XXX 的共享函数(如果以 C# 编写则为静态函数),其中的 XXX 是要实例化的页面类的名称。顾名思义,这是一个帮助器类,ASP.NET 运行库利用其来加速页面实例的创建 - 这是一个非常常见的操作。与编译一个页面相比,创建这种类所花费的时间非常短。另一方面,使用工厂类比使用 Activator.CreateInstance 间接创建对象要快得多。

根据批处理编译设置,工厂类的内容会有所变化。在默认情况下,当批处理开启时,工厂类包含与批处理页面相同数量的 Create_XXX 函数。工厂类的名称与批处理程序集的名称相同:

' Used to serve test.aspx
Shared Function Create_ASP_test_aspx() As Object
Return New ASP.test_aspx
End Function
' Used to serve default.aspx
Shared Function Create_ASP_default_aspx() As Object
Return New ASP.default_aspx
End Function

 

如果批处理关闭,则工厂类与单个页面程序集的名称相同,并且只包含一个共享函数 - 具体页面的 facotry。在这种情况下,应用程序中的每个页面将有自己的工厂类。

Back to top


运行库公共 API

借助上面讨论的信息,探究编译文件夹的内容就不是非常困难了。但通过一个工具来帮助您快速找到您所需的信息还是非常方便。 我待会儿将设计一个用来导航动态生成的 ASP.NET 应用程序源代码的资源管理器工具,但首先我们来看一看 .NET Framework 2.0 中的一些运行库 API。特别是,以下两个类可能是您更希望了解的:HttpRuntime 和 ClientBuildManager。

HttpRuntime 类具有大量共享属性,可返回关于包括当前应用程序的 Bin 文件夹、ASP.NET 安装路径、编译文件夹和当前 AppDomain ID 在内的各种系统路径的信息。您还可以使用以下代码轻松获取当前 AppDomain 中加载的程序集列表:

Dim listOfAssemblies() As Assembly
listOfAssemblies = AppDomain.CurrentDomain.GetAssemblies()

 

此代码并非特定于 ASP.NET,但当从 ASP.NET 应用程序内部调用时,它将返回包含 AppDomain 中的程序集的数组,其中包括为您的页面生成的所有程序集。

ClientBuildManager 类没有多少信息一类的属性,CodeGenDir 属性除外,该属性返回与 HttpRuntime 的 CodeGenDir 属性相同的信息。但 ClientBuildManager 具有许多读取配置信息(如支持的浏览器)的方法和预编译应用程序的方法。Get 是该类中的一个方法,它返回一列应用程序的目录(在这些目录中监视那些会引发关闭 AppDomain 应用程序的重要更改)。这些目录是:App_Browsers、App_Code、App_GlobalResources、App_WebReferences 和 Bin。

Back to top


构建资源管理器工具

对于调试,能够快速访问正在运行的页面的源代码和其他运行时信息往往非常有用。任何提供这种功能的工具都必须与所有 ASP.NET 应用程序兼容,并且只要求进行有限的配置或根本无需配置。Nikhil Kothari 出色的 Web Development Helper 工具如果能够提供 ASP.NET 运行库信息,那就非常完美了。该工具作为浏览器帮助对象 (BHO) 来实现,BHO 是一种用于 Microsoft Internet Explorer® 用户界面的基于 COM 的插件。BHO 对于我在本专栏中构建的监视工具将是非常好的宿主环境,但可惜我偷了些懒,并没有这样做。因此我将我的工具编写为位于页面和浏览器之间的一个 HTTP 模块,它可查找查询字符串,如果是显式调用就可发挥作用。在 ASP.NET 应用程序中安装 HTTP 模块只需在 web.config 中增加一行语句,而且可以非常容易地开启和关闭安装:

<httpModules>
<add name="AspExplorerModule" type="Samples.AspExplorerModule" />
</httpModules>

 

图 6 显示了 Explorer HTTP 模块的大部分代码。该模块注册使用 PostMapRequestHandler 应用程序事件并与页面类挂接。PostMapRequestHandler 事件会在 ASP.NET 运行库确定了为请求提供服务所需的 HTTP 处理程序对象时触发。如果请求的查询字符串中包含 source=true 参数,并且处理程序是从 System.Web.UI.Page 继承的一个类,那么模块将开始工作。

ASP Explorer 模块会与页面类挂接,并为 PreRenderComplete 事件注册其自己的处理程序。这样的设计使得 HTTP 模块不会改变请求的运行时处理,也不会干预页面的编译。当查询字符串指定了 source 参数并将其设置为 true 时,模块就会发挥作用。如图 6 中所示,模块所要做的就是使用不太常见的“页面”类方法 SetRenderMethodDelegate 为页面注册呈现委派 (rendering delegate)。当为页面指定了呈现委派时,所封装的方法会替代标准呈现处理。换言之,一旦安装了该模块,如果使用 test.aspx 进行调用,您将看到页面的标准输出;如果您使用 test.aspx?source=true 进行调用,您将看到模块可收集的所有与页面有关的运行时信息。

ASP Explorer 源代码定义了一个类,以映射当前页的保留文件的内容。它会读取保留文件,并复制图 7 所示的类中的所有信息。SourceFiles 属性是设计用于包含页面使用的所有源文件的一个集合。此集合包含从编译文件夹获得的保留文件中所没有的信息。特别是其中包括与某个页面相关的 .vb 或 .cs 格式的所有源文件,这些文件名以动态页面程序集的名称开头。GetWebPageInfo 方法(请参见图 6)捕获所有信息并为 source 模式的请求构建输出内容。页面输出包括运行时信息和动态页面类的源代码。图 8 显示了实际运行中的 ASP Explorer。

图 8 实际运行中的 ASP Explorer 模块
图 8 实际运行中的 ASP Explorer 模块 (单击该图像获得较大视图)
Back to top


示例页面分析

既然有了可使用的工具,那么让我们简要查看一下 ASP.NET 为每个 .aspx 文件生成的代码的结构。值得注意的是,如果没有 ASP.NET 运行库提供的分析和编译工具,您就必须亲自编写代码来运行 ASP.NET 页面!

动态页面类(图 5 中的 test_aspx 类)改写了 System.Web.UI.Page 类中的几个方法:FrameworkInitialize、ProcessRequest 和 GetTypeHashCode。ProcessRequest 没有什么变化,它只是调用它的基类方法。GetTypeHashCode 返回页面的散列代码,该代码可唯一标识页面的控件层次结构。当对页面进行编译时,会动态计算散列值,并将其作为常量插入到源文件。

最值得关注的是对 FrameworkInitialize 的改写。该方法控制页面的控件树的创建,并调入一个名为 __BuildControlTree 的私有方法。此方法使用与 .aspx 源文件中的 runat=server 标记相对应的控件的新实例来填充页面类的 Control 集合。__BuildControlTree 会分析所有服务器端标记并为每个标记构建一个对象。

<asp:textbox runat="server" id="TextBox1" text="Type here" />

 

以下是为上述标记获取的典型代码:

Private Function __BuildControlTextBox1() As TextBox
Dim __ctrl As New TextBox()
Me.TextBox1 = __ctrl
__ctrl.ApplyStyleSheetSkin(Me)
__ctrl.ID = "TextBox1"
__ctrl.Text = "Type here"
Return __ctrl
End Function

 

如果控件有事件处理程序或数据绑定表达式,会怎样?让我们首先来考虑带“单击”事件处理程序的按钮。您需要增加一行语句:

__ AddHandler __ctrl.Click, AddressOf Me.Button1_Click

 

对于数据绑定表达式 <%# … %>,除了使用了 DataBinding 事件,生成的代码与之类似:

AddHandler __ctrl.DataBinding, AddressOf Me.DataBindingMsg

 

与处理程序相关的代码取决于绑定的控件的属性和要绑定的代码。对于 Label 控件的 Text 属性,代码类似于:

Public Sub DataBindingMsg(ByVal sender As Object, ByVal e As EventArgs)
Dim target As Label = DirectCast(sender, Label)
target.Text = Convert.ToString(..., _
CultureInfo.CurrentCulture);
End Sub

 

传递给 Convert.ToString 的表达式就是 <%# … %> 表达式中的代码。强制类型转换还取决于所涉及的类型。

如果存在母版页和主题,那么源文件的数量和依存关系列表就会增大,但借助 ASP Explorer 工具,您可以随意对其进行跟踪。

Back to top


总结

ASP.NET 对其拥有的资源类型执行按需动态代码编译。此功能大大促进了 Web 应用程序的快速迭代开发,但需要 ASP.NET 才能将文件写到磁盘。编译文件夹是一个重要的文件夹,ASP.NET 的许多神奇之处都在此体现。您可以只是出于兴趣对此文件夹研究一番,有时却可以利用它来诊断和调试棘手的问题。当然,这里讨论的大部分功能是 ASP.NET 内部的功能,因此来说,这些功能在未来版本中可能会未经提醒即进行更改。 但截至目前为止,ASP.NET 2.0 的工作原理就是如本文所述的这样。顺便提一下,可以将 ASP Explorer 工具与 ASP.NET AJAX 应用程序一起使用,这一点也请放心。 该工具的运行效果非常好。

Back to top

 

posted @ 2008-01-24 13:37  NanKe Sir's Blog  阅读(3330)  评论(0编辑  收藏  举报