Category Archives: .NET

深入探索.NET框架内部了解CLR如何创建运行时对象

本文讨论:

SystemDomain, SharedDomain, and DefaultDomain
对象布局和内存细节。
方法表布局。
方法分派(Method dispatching)。

本文使用下列技术: .NET Framework, C#

本页内容
 CLR启动程序(Bootstrap)创建的域 CLR启动程序(Bootstrap)创建的域
系统域(System Domain) 系统域(System Domain)
共享域(Shared Domain) 共享域(Shared Domain)
默认域(Default Domain) 默认域(Default Domain)
加载器堆(Loader Heaps) 加载器堆(Loader Heaps)
类型原理 类型原理
对象实例 对象实例
方法表 方法表
基实例大小 基实例大小
方法槽表(Method Slot Table) 方法槽表(Method Slot Table)
方法描述(MethodDesc) 方法描述(MethodDesc)
接口虚表图和接口图 接口虚表图和接口图
虚分派(Virtual Dispatch) 虚分派(Virtual Dispatch)
静态变量 静态变量
EEClass EEClass
Conclusion结论 Conclusion结论

随着通用语言运行时(CLR)即将成为在Windows®下开发应用程序的首选架构,对其进行深入理解会帮助你建立有效的工业强度的应用程序。在本文中,我们将探索CLR内部,包括对象实例布局,方法表布局,方法分派,基于接口的分派和不同的数据结构。

我们将使用C#编写的简单代码示例,以便任何固有的语言语法含义是C#的缺省定义。某些此处讨论的数据结构和算法可能会在Microsoft® .NET Framework 2.0中改变,但是主要概念应该保持不变。我们使用Visual Studio® .NET 2003调试器和调试器扩展Son of Strike (SOS)来查看本文讨论的数据结构。SOS理解CLR的内部数据结构并输出有用信息。请参考“Son of Strike”补充资料,了解如何将SOS.dll装入Visual Studio .NET 2003调试器的进程空间。本文中,我们将描述在共享源代码CLI(Shared Source CLI,SSCLI)中有相应实现的类,你可以从msdn.microsoft.com/net/sscli下载。图1将帮助你在SSCLI的数以兆计的代码中找到所参考的结构。

在我们开始前,请注意:本文提供的信息只对在X86平台上运行的.NET Framework 1.1有效(对于Shared Source CLI 1.0也大部分适用,只是在某些交互操作的情况下必须注意例外),对于.NET Framework 2.0会有改变,所以请不要在构建软件时依赖于这些内部结构的不变性。

CLR启动程序(Bootstrap)创建的域

在CLR执行托管代码的第一行代码前,会创建三个应用程序域。其中两个对于托管代码甚至CLR宿主程序(CLR hosts)都是不可见的。它们只能由CLR启动进程创建,而提供CLR启动进程的是shim——mscoree.dll和mscorwks.dll (在多处理器系统下是mscorsvr.dll)。正如2所示,这些域是系统域(System Domain)和共享域(Shared Domain),都是使用了单件(Singleton)模式。第三个域是缺省应用程序域(Default AppDomain),它是一个AppDomain的实例,也是唯一的有命名的域。对于简单的CLR宿主程序,比如控制台程序,默认的域名由可执行映象文件的名字组成。其它的域可以在托管代码中使用AppDomain.CreateDomain方法创建,或者在非托管的代码中使用ICORRuntimeHost接口创建。复杂的宿主程序,比如ASP.NET,对于特定的网站会基于应用程序的数目创建多个域。

2 由CLR启动程序创建的域

系统域(System Domain)

系统域负责创建和初始化共享域和默认应用程序域。它将系统库mscorlib.dll载入共享域,并且维护进程范围内部使用的隐含或者显式字符串符号。

字符串驻留(string interning)是.NET Framework 1.1中的一个优化特性,它的处理方法显得有些笨拙,因为CLR没有给程序集机会选择此特性。尽管如此,由于在所有的应用程序域中对一个特定的符号只保存一个对应的字符串,此特性可以节省内存空间。

系统域还负责产生进程范围的接口ID,并用来创建每个应用程序域的接口虚表映射图(InterfaceVtableMaps)的接口。系统域在进程中保持跟踪所有域,并实现加载和卸载应用程序域的功能。

共享域(Shared Domain)

所有不属于任何特定域的代码被加载到系统库SharedDomain.Mscorlib,对于所有应用程序域的用户代码都是必需的。它会被自动加载到共享域中。系统命名空间的基本类型,如Object, ValueType, Array, Enum, String, and Delegate等等,在CLR启动程序过程中被预先加载到本域中。用户代码也可以被加载到这个域中,方法是在调用CorBindToRuntimeEx时使用由CLR宿主程序指定的LoaderOptimization特性。控制台程序也可以加载代码到共享域中,方法是使用System.LoaderOptimizationAttribute特性声明Main方法。共享域还管理一个使用基地址作为索引的程序集映射图,此映射图作为管理共享程序集依赖关系的查找表,这些程序集被加载到默认域(DefaultDomain)和其它在托管代码中创建的应用程序域。非共享的用户代码被加载到默认域。

默认域(Default Domain)

默认域是应用程序域(AppDomain)的一个实例,一般的应用程序代码在其中运行。尽管有些应用程序需要在运行时创建额外的应用程序域(比如有些使用插件,plug-in,架构或者进行重要的运行时代码生成工作的应用程序),大部分的应用程序在运行期间只创建一个域。所有在此域运行的代码都是在域层次上有上下文限制。如果一个应用程序有多个应用程序域,任何的域间访问会通过.NET Remoting代理。额外的域内上下文限制信息可以使用System.ContextBoundObject派生的类型创建。每个应用程序域有自己的安全描述符(SecurityDescriptor),安全上下文(SecurityContext)和默认上下文(DefaultContext),还有自己的加载器堆(高频堆,低频堆和代理堆),句柄表,接口虚表管理器和程序集缓存。

加载器堆(Loader Heaps)

加载器堆的作用是加载不同的运行时CLR部件和优化在域的整个生命期内存在的部件。这些堆的增长基于可预测块,这样可以使碎片最小化。加载器堆不同于垃圾回收堆(或者对称多处理器上的多个堆),垃圾回收堆保存对象实例,而加载器堆同时保存类型系统。经常访问的部件如方法表,方法描述,域描述和接口图,分配在高频堆上,而较少访问的数据结构如EEClass和类加载器及其查找表,分配在低频堆。代理堆保存用于代码访问安全性(code access security, CAS)的代理部件,如COM封装调用和平台调用(P/Invoke)。

从高层次了解域后,我们准备看看它们在一个简单的应用程序的上下文中的物理细节,见图3。我们在程序运行时停在mc.Method1(),然后使用SOS调试器扩展命令DumpDomain来输出域的信息。(请查看Son of Strike了解SOS的加载信息)。这里是编辑后的输出:

!DumpDomain
System Domain: 793e9d58, LowFrequencyHeap: 793e9dbc,
HighFrequencyHeap: 793e9e14, StubHeap: 793e9e6c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40
Shared Domain: 793eb278, LowFrequencyHeap: 793eb2dc,
HighFrequencyHeap: 793eb334, StubHeap: 793eb38c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40
Domain 1: 149100, LowFrequencyHeap: 00149164,
HighFrequencyHeap: 001491bc, StubHeap: 00149214,
Name: Sample1.exe, Assembly: 00164938 [Sample1],
ClassLoader: 00164a78

我们的控制台程序,Sample1.exe,被加载到一个名为“Sample1.exe”的应用程序域。Mscorlib.dll被加载到共享域,不过因为它是核心系统库,所以也在系统域中列出。每个域会分配一个高频堆,低频堆和代理堆。系统域和共享域使用相同的类加载器,而默认应用程序使用自己的类加载器。

输出没有显示加载器堆的保留尺寸和已提交尺寸。高频堆的初始化大小是32KB,每次提交4KB。SOS的输出也没有显示接口虚表堆(InterfaceVtableMap)。每个域有一个接口虚表堆(简称为IVMap),由自己的加载器堆在域初始化阶段创建。IVMap保留大小是4KB,开始时提交4KB。我们将会在后续部分研究类型布局时讨论IVMap的意义。

2显示默认的进程堆,JIT代码堆,GC堆(用于小对象)和大对象堆(用于大小等于或者超过85000字节的对象),它说明了这些堆和加载器堆的语义区别。即时(just-in-time, JIT)编译器产生x86指令并且保存到JIT代码堆中。GC堆和大对象堆是用于托管对象实例化的垃圾回收堆。

类型原理

类型是.NET编程中的基本单元。在C#中,类型可以使用class,struct和interface关键字进行声明。大多数类型由程序员显式创建,但是,在特别的交互操作(interop)情形和远程对象调用(.NET Remoting)场合中,.NET CLR会隐式的产生类型,这些产生的类型包含COM和运行时可调用封装及传输代理(Runtime Callable Wrappers and Transparent Proxies)。

我们通过一个包含对象引用的栈开始研究.NET类型原理(典型地,栈是一个对象实例开始生命期的地方)。4中显示的代码包含一个简单的程序,它有一个控制台的入口点,调用了一个静态方法。Method1创建一个SmallClass的类型实例,该类型包含一个字节数组,用于演示如何在大对象堆创建对象。尽管这是一段无聊的代码,但是可以帮助我们进行讨论。

5显示了停止在Create方法“return smallObj;”代码行断点时的fastcall栈结构(fastcall时.NET的调用规范,它说明在可能的情况下将函数参数通过寄存器传递,而其它参数按照从右到左的顺序入栈,然后由被调用函数完成出栈操作)。本地值类型变量objSize内含在栈结构中。引用类型变量如smallObj以固定大小(4字节DWORD)保存在栈中,包含了在一般GC堆中分配的对象的地址。对于传统C++,这是对象的指针;在托管世界中,它是对象的引用。不管怎样,它包含了一个对象实例的地址,我们将使用术语对象实例(ObjectInstance)描述对象引用指向地址位置的数据结构。

5 SimpleProgram的栈结构和堆

一般GC堆上的smallObj对象实例包含一个名为_largeObj的字节数组(注意,图中显示的大小为85016字节,是实际的存贮大小)。CLR对大于或等于85000字节的对象的处理和小对象不同。大对象在大对象堆(LOH)上分配,而小对象在一般GC堆上创建,这样可以优化对象的分配和回收。LOH不会压缩,而GC堆在GC回收时进行压缩。还有,LOH只会在完全GC回收时被回收。

smallObj的对象实例包含类型句柄(TypeHandle),指向对应类型的方法表。每个声明的类型有一个方法表,而同一类型的所有对象实例都指向同一个方法表。它包含了类型的特性信息(接口,抽象类,具体类,COM封装和代理),实现的接口数目,用于接口分派的接口图,方法表的槽(slot)数目,指向相应实现的槽表。

方法表指向一个名为EEClass的重要数据结构。在方法表创建前,CLR类加载器从元数据中创建EEClass。图4中,SmallClass的方法表指向它的EEClass。这些结构指向它们的模块和程序集。方法表和EEClass一般分配在共享域的加载器堆。加载器堆和应用程序域关联,这里提到的数据结构一旦被加载到其中,就直到应用程序域卸载时才会消失。而且,默认的应用程序域不会被卸载,所以这些代码的生存期是直到CLR关闭为止。

对象实例

正如我们说过的,所有值类型的实例或者包含在线程栈上,或者包含在GC堆上。所有的引用类型在GC堆或者LOH上创建。图6显示了一个典型的对象布局。一个对象可以通过以下途径被引用:基于栈的局部变量,在交互操作或者平台调用情况下的句柄表,寄存器(执行方法时的this指针和方法参数),拥有终结器(finalizer)方法的对象的终结器队列。OBJECTREF不是指向对象实例的开始位置,而是有一个DWORD的偏移量(4字节)。此DWORD称为对象头,保存一个指向SyncTableEntry表的索引(从1开始计数的syncblk编号。因为通过索引进行连接,所以在需要增加表的大小时,CLR可以在内存中移动这个表。SyncTableEntry维护一个反向的弱引用,以便CLR可以跟踪SyncBlock的所有权。弱引用让GC可以在没有其它强引用存在时回收对象。SyncTableEntry还保存了一个指向SyncBlock的指针,包含了很少需要被一个对象的所有实例使用的有用的信息。这些信息包括对象锁,哈希编码,任何转换层(thunking)数据和应用程序域的索引。对于大多数的对象实例,不会为实际的SyncBlock分配内存,而且syncblk编号为0。这一点在执行线程遇到如lock(obj)或者obj.GetHashCode的语句时会发生变化,如下所示:

SmallClass obj = new SmallClass()
// Do some work here
lock(obj) { /* Do some synchronized work here */ }
obj.GetHashCode();

在以上代码中,smallObj会使用0作为它的起始的syncblk编号。lock语句使得CLR创建一个syncblk入口并使用相应的数值更新对象头。因为C#的lock关键字会扩展为try-finally语句并使用Monitor类,一个用作同步的Monitor对象在syncblk上创建。堆GetHashCode的调用会使用对象的哈希编码增加syncblk。

在SyncBlock中有其它的域,它们在COM交互操作和封送委托(marshaling delegates)到非托管代码时使用,不过这和典型的对象用处无关。

类型句柄紧跟在对象实例中的syncblk编号后。为了保持连续性,我会在说明实例变量后讨论类型句柄。实例域(Instance field)的变量列表紧跟在类型句柄后。默认情况下,实例域会以内存最有效使用的方式排列,这样只需要最少的用作对齐的填充字节。图7的代码显示了SimpleClass包含有一些不同大小的实例变量。

图8显示了在Visual Studio调试器的内存窗口中的一个SimpleClass对象实例。我们在图7的return语句处设置了断点,然后使用ECX寄存器保存的simpleObj地址在内存窗口显示对象实例。前4个字节是syncblk编号。因为我们没有用任何同步代码使用此实例(也没有访问它的哈希编码),syncblk编号为0。保存在栈变量的对象实例,指向起始位置的4个字节的偏移处。字节变量b1,b2,b3和b4被一个接一个的排列在一起。两个short类型变量s1和s2也被排列在一起。字符串变量str是一个4字节的OBJECTREF,指向GC堆中分配的实际的字符串实例。字符串是一个特别的类型,因为所有包含同样文字符号的字符串,会在程序集加载到进程时指向一个全局字符串表的同一实例。这个过程称为字符串驻留(string interning),设计目的是优化内存的使用。我们之前已经提过,在NET Framework 1.1中,程序集不能选择是否使用这个过程,尽管未来版本的CLR可能会提供这样的能力。

所以默认情况下,成员变量在源代码中的词典顺序没有在内存中保持。在交互操作的情况下,词典顺序必须被保存到内存中,这时可以使用StructLayoutAttribute特性,它有一个LayoutKind的枚举类型作为参数。LayoutKind.Sequential可以为被封送(marshaled)数据保持词典顺序,尽管在.NET Framework 1.1中,它没有影响托管的布局(但是.NET Framework 2.0可能会这么做)。在交互操作的情况下,如果你确实需要额外的填充字节和显示的控制域的顺序,LayoutKind.Explicit可以和域层次的FieldOffset特性一起使用。

看完底层的内存内容后,我们使用SOS看看对象实例。一个有用的命令是DumpHeap,它可以列出所有的堆内容和一个特别类型的所有实例。无需依赖寄存器,DumpHeap可以显示我们创建的唯一一个实例的地址。

!DumpHeap -type SimpleClass
Loaded Son of Strike data table version 5 from
"C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"
 Address       MT     Size
00a8197c 00955124       36
Last good object: 00a819a0
total 1 objects
Statistics:
      MT    Count TotalSize Class Name
  955124        1        36 SimpleClass

对象的总大小是36字节,不管字符串多大,SimpleClass的实例只包含一个DWORD的对象引用。SimpleClass的实例变量只占用28字节,其它8个字节包括类型句柄(4字节)和syncblk编号(4字节)。找到simpleObj实例的地址后,我们可以使用DumpObj命令输出它的内容,如下所示:

!DumpObj 0x00a8197c
Name: SimpleClass
MethodTable 0x00955124
EEClass 0x02ca33b0
Size 36(0x24) bytes
FieldDesc*: 00955064
      MT    Field   Offset                 Type       Attr    Value Name
00955124  400000a        4         System.Int64   instance      31 l1
00955124  400000b        c                CLASS   instance 00a819a0 str
    << some fields omitted from the display for brevity >>
00955124  4000003       1e          System.Byte   instance        3 b3
00955124  4000004       1f          System.Byte   instance        4 b4

正如之前说过,C#编译器对于类的默认布局使用LayoutType.Auto(对于结构使用LayoutType.Sequential);因此类加载器重新排列实例域以最小化填充字节。我们可以使用ObjSize来输出包含被str实例占用的空间,如下所示:

!ObjSize 0x00a8197c
sizeof(00a8197c) =       72 (    0x48) bytes (SimpleClass)

如果你从对象图的全局大小(72字节)减去SimpleClass的大小(36字节),就可以得到str的大小,即36字节。让我们输出str实例来验证这个结果:

!DumpObj 0x00a819a0
Name: System.String
MethodTable 0x009742d8
EEClass 0x02c4c6c4
Size 36(0x24) bytes

如果你将字符串实例的大小(36字节)加上SimpleClass实例的大小(36字节),就可以得到ObjSize命令报告的总大小72字节。

请注意ObjSize不包含syncblk结构占用的内存。而且,在.NET Framework 1.1中,CLR不知道非托管资源占用的内存,如GDI对象,COM对象,文件句柄等等;因此它们不会被这个命令报告。

指向方法表的类型句柄在syncblk编号后分配。在对象实例创建前,CLR查看加载类型,如果没有找到,则进行加载,获得方法表地址,创建对象实例,然后把类型句柄值追加到对象实例中。JIT编译器产生的代码在进行方法分派时使用类型句柄来定位方法表。CLR在需要史可以通过方法表反向访问加载类型时使用类型句柄。

方法表

每个类和实例在加载到应用程序域时,会在内存中通过方法表来表示。这是在对象的第一个实例创建前的类加载活动的结果。对象实例表示的是状态,而方法表表示了行为。通过EEClass,方法表把对象实例绑定到被语言编译器产生的映射到内存的元数据结构(metadata structures)。方法表包含的信息和外挂的信息可以通过System.Type访问。指向方法表的指针在托管代码中可以通过Type.RuntimeTypeHandle属性获得。对象实例包含的类型句柄指向方法表开始位置的偏移处,偏移量默认情况下是12字节,包含了GC信息。我们不打算在这里对其进行讨论。

图9显示了方法表的典型布局。我们会说明类型句柄的一些重要的域,但是对于完全的列表,请参看此图。让我们从基实例大小(Base Instance Size)开始,因为它直接关系到运行时的内存状态。

基实例大小

基实例大小是由类加载器计算的对象的大小,基于代码中声明的域。之前已经讨论过,当前GC的实现需要一个最少12字节的对象实例。如果一个类没有定义任何实例域,它至少包含额外的4个字节。其它的8个字节被对象头(可能包含syncblk编号)和类型句柄占用。再说一次,对象的大小会受到StructLayoutAttribute的影响。

看看图3中显示的MyClass(有两个接口)的方法表的内存快照(Visual Studio .NET 2003内存窗口),将它和SOS的输出进行比较。在图9中,对象大小位于4字节的偏移处,值为12(0x0000000C)字节。以下是SOS的DumpHeap命令的输出:

!DumpHeap -type MyClass
 Address       MT     Size
00a819ac 009552a0       12
total 1 objects
Statistics:
    MT  Count TotalSize Class Name
9552a0      1        12    MyClass

方法槽表(Method Slot Table)

在方法表中包含了一个槽表,指向各个方法的描述(MethodDesc),提供了类型的行为能力。方法槽表是基于方法实现的线性链表,按照如下顺序排列:继承的虚方法,引入的虚方法,实例方法,静态方法。

类加载器在当前类,父类和接口的元数据中遍历,然后创建方法表。在排列过程中,它替换所有的被覆盖的虚方法和被隐藏的父类方法,创建新的槽,在需要时复制槽。槽复制是必需的,它可以让每个接口有自己的最小的vtable。但是被复制的槽指向相同的物理实现。MyClass包含接口方法,一个类构造函数(.cctor)和对象构造函数(.ctor)。对象构造函数由C#编译器为所有没有显式定义构造函数的对象自动生成。因为我们定义并初始化了一个静态变量,编译器会生成一个类构造函数。10显示了MyClass的方法表的布局。布局显示了10个方法,因为Method2槽为接口IVMap进行了复制,下面我们会进行讨论。图11显示了MyClass的方法表的SOS的输出。

任何类型的开始4个方法总是ToString, Equals, GetHashCode, and Finalize。这些是从System.Object继承的虚方法。Method2槽被进行了复制,但是都指向相同的方法描述。代码显示定义的.cctor和.ctor会分别和静态方法和实例方法分在一组。

方法描述(MethodDesc)

方法描述(MethodDesc)是CLR知道的方法实现的一个封装。有几种类型的方法描述,除了用于托管实现,分别用于不同的交互操作实现的调用。在本文中,我们只考察图3代码中的托管方法描述。方法描述在类加载过程中产生,初始化为指向IL。每个方法描述带有一个预编译代理(PreJitStub),负责触发JIT编译。图12显示了一个典型的布局,方法表的槽实际上指向代理,而不是实际的方法描述数据结构。对于实际的方法描述,这是-5字节的偏移,是每个方法的8个附加字节的一部分。这5个字节包含了调用预编译代理程序的指令。5字节的偏移可以从SOS的DumpMT输出从看到,因为方法描述总是方法槽表指向的位置后面的5个字节。在第一次调用时,会调用JIT编译程序。在编译完成后,包含调用指令的5个字节会被跳转到JIT编译后的x86代码的无条件跳转指令覆盖。

12 方法描述

对图12的方法表槽指向的代码进行反汇编,显示了对预编译代理的调用。以下是在Method2被JIT编译前的反汇编的简化显示。

!u 0x00955263
Unmanaged code
00955263 call        003C3538        ;call to the jitted Method2()
00955268 add         eax,68040000h   ;ignore this and the rest
                                     ;as !u thinks it as code

现在我们执行此方法,然后反汇编相同的地址:

!u 0x00955263
Unmanaged code
00955263 jmp     02C633E8        ;call to the jitted Method2()
00955268 add     eax,0E8040000h  ;ignore this and the rest
                                 ;as !u thinks it as code

在此地址,只有开始5个字节是代码,剩余字节包含了Method2的方法描述的数据。“!u”命令不知道这一点,所以生成的是错乱的代码,你可以忽略5个字节后的所有东西。

CodeOrIL在JIT编译前包含IL中方法实现的相对虚地址(Relative Virtual Address ,RVA)。此域用作标志,表示是否IL。在按要求编译后,CLR使用编译后的代码地址更新此域。让我们从列出的函数中选择一个,然后用DumpMT命令分别输出在JIT编译前后的方法描述的内容:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
IL RVA : 00002068

编译后,方法描述的内容如下:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
Method VA : 02c633e8

方法的这个标志域的编码包含了方法的类型,例如静态,实例,接口方法或者COM实现。让我们看方法表另外一个复杂的方面:接口实现。它封装了布局过程所有的复杂性,让托管环境觉得这一点看起来简单。然后,我们将说明接口如何进行布局和基于接口的方法分派的确切工作方式。

接口虚表图和接口图

在方法表的第12字节偏移处是一个重要的指针,接口虚表(IVMap)。如图9所示,接口虚表指向一个应用程序域层次的映射表,该表以进程层次的接口ID作为索引。接口ID在接口类型第一次加载时创建。每个接口的实现都在接口虚表中有一个记录。如果MyInterface1被两个类实现,在接口虚表表中就有两个记录。该记录会反向指向MyClass方法表内含的子表的开始位置,如图9所示。这是接口方法分派发生时使用的引用。接口虚表是基于方法表内含的接口图信息创建,接口图在方法表布局过程中基于类的元数据创建。一旦类型加载完成,只有接口虚表用于方法分派。

第28字节位置的接口图会指向内含在方法表中的接口信息记录。在这种情况下,对MyClass实现的两个接口中的每一个都有两条记录。第一条接口信息记录的开始4个字节指向MyInterface1的类型句柄(见图9图10)。接着的WORD(2字节)被一个标志占用(0表示从父类派生,1表示由当前类实现)。在标志后的WORD是一个开始槽(Start Slot),被类加载器用来布局接口实现的子表。对于MyInterface2,开始槽的值为4(从0开始编号),所以槽5和6指向实现;对于MyInterface2,开始槽的值为6,所以槽7和8指向实现。类加载器会在需要时复制槽来产生这样的效果:每个接口有自己的实现,然而物理映射到同样的方法描述。在MyClass中,MyInterface1.Method2和MyInterface2.Method2会指向相同的实现。

基于接口的方法分派通过接口虚表进行,而直接的方法分派通过保存在各个槽的方法描述地址进行。如之前提及,.NET框架使用fastcall的调用约定,最先2个参数在可能的时候一般通过ECX和EDX寄存器传递。实例方法的第一个参数总是this指针,所以通过ECX寄存器传送,可以在“mov ecx,esi”语句看到这一点:

mi1.Method1();
mov    ecx,edi                 ;move "this" pointer into ecx
mov    eax,dword ptr [ecx]     ;move "TypeHandle" into eax
mov    eax,dword ptr [eax+0Ch] ;move IVMap address into eax at offset 12
mov    eax,dword ptr [eax+30h] ;move the ifc impl start slot into eax
call   dword ptr [eax]         ;call Method1
mc.Method1();
mov    ecx,esi                 ;move "this" pointer into ecx
cmp    dword ptr [ecx],ecx     ;compare and set flags
call   dword ptr ds:[009552D8h];directly call Method1

这些反汇编显示了直接调用MyClass的实例方法没有使用偏移。JIT编译器把方法描述的地址直接写到代码中。基于接口的分派通过接口虚表发生,和直接分派相比需要一些额外的指令。一个指令用来获得接口虚表的地址,另一个获取方法槽表中的接口实现的开始槽。而且,把一个对象实例转换为接口只需要拷贝this指针到目标的变量。在图2中,语句“mi1=mc”使用一个指令把mc的对象引用拷贝到mi1。

虚分派(Virtual Dispatch)

现在我们看看虚分派,并且和基于接口的分派进行比较。以下是图3中MyClass.Method3的虚函数调用的反汇编代码:

mc.Method3();
Mov    ecx,esi               ;move "this" pointer into ecx
Mov    eax,dword ptr [ecx]   ;acquire the MethodTable address
Call   dword ptr [eax+44h]   ;dispatch to the method at offset 0x44

虚分派总是通过一个固定的槽编号发生,和方法表指针在特定的类(类型)实现层次无关。在方法表布局时,类加载器用覆盖的子类的实现代替父类的实现。结果,对父对象的方法调用被分派到子对象的实现。反汇编显示了分派通过8号槽发生,可以在调试器的内存窗口(如图10所示)和DumpMT的输出看到这一点。

静态变量

静态变量是方法表数据结构的重要组成部分。作为方法表的一部分,它们分配在方法表的槽数组后。所有的原始静态类型是内联的,而对于结构和引用的类型的静态值对象,通在句柄表中创建的对象引用来指向。方法表中的对象引用指向应用程序域的句柄表的对象引用,它引用了堆上创建的对象实例。一旦创建后,句柄表内的对象引用会使堆上的对象实例保持生存,直到应用程序域被卸载。在图9 中,静态字符串变量str指向句柄表的对象引用,后者指向GC堆上的MyString。

EEClass

EEClass在方法表创建前开始生存,它和方法表结合起来,是类型声明的CLR版本。实际上,EEClass和方法表逻辑上是一个数据结构(它们一起表示一个类型),只不过因为使用频度的不同而被分开。经常使用的域放在方法表,而不经常使用的域在EEClass中。这样,需要被JIT编译函数使用的信息(如名字,域和偏移)在EEClass中,但是运行时需要的信息(如虚表槽和GC信息)在方法表中。

对每一个类型会加载一个EEClass到应用程序域中,包括接口,类,抽象类,数组和结构。每个EEClass是一个被执行引擎跟踪的树的节点。CLR使用这个网络在EEClass结构中浏览,其目的包括类加载,方法表布局,类型验证和类型转换。EEClass的子-父关系基于继承层次建立,而父-子关系基于接口层次和类加载顺序的结合。在执行托管代码的过程中,新的EEClass节点被加入,节点的关系被补充,新的关系被建立。在网络中,相邻的EEClass还有一个水平的关系。EEClass有三个域用于管理被加载类型的节点关系:父类(Parent Class),相邻链(sibling chain)和子链(children chain)。关于图4中的MyClass上下文中的EEClass的语义,请参考图13。

13只显示了和这个讨论相关的一些域。因为我们忽略了布局中的一些域,我们没有在图中确切显示偏移。EEClass有一个间接的对于方法表的引用。EEClass也指向在默认应用程序域的高频堆分配的方法描述块。在方法表创建时,对进程堆上分配的域描述列表的一个引用提供了域的布局信息。EEClass在应用程序域的低频堆分配,这样操作系统可以更好的进行内存分页管理,因此减少了工作集。

13 EEClass 布局

图13中的其它域在MyClass(图3)的上下文的意义不言自明。我们现在看看使用SOS输出的EEClass的真正的物理内存。在mc.Method1代码行设置断点后,运行图3的程序。首先使用命令Name2EE获得MyClass的EEClass的地址。

!Name2EE C:\Working\test\ClrInternals\Sample1.exe MyClass
MethodTable: 009552a0
EEClass: 02ca3508
Name: MyClass

Name2EE的第一个参数时模块名,可以从DumpDomain命令得到。现在我们得到了EEClass的地址,我们输出EEClass:

!DumpClass 02ca3508
Class Name : MyClass, mdToken : 02000004, Parent Class : 02c4c3e4
ClassLoader : 00163ad8, Method Table : 009552a0, Vtable Slots : 8
Total Method Slots : a, NumInstanceFields: 0,
NumStaticFields: 2,FieldDesc*: 00955224
      MT    Field   Offset  Type           Attr    Value    Name
009552a0  4000001   2c      CLASS          static 00a8198c  str
009552a0  4000002   30      System.UInt32  static aaaaaaaa  ui

13和DumpClass的输出看起来完全一样。元数据令牌(metadata token,mdToken)表示了在模块PE文件中映射到内存的元数据表的MyClass索引,父类指向System.Object。从相邻链指向名为Program的EEClass,可以知道图13显示的是加载Program时的结果。

MyClass有8个虚表槽(可以被虚分派的方法)。即使Method1和Method2不是虚方法,它们可以在通过接口进行分派时被认为是虚函数并加入到列表中。把.cctor和.ctor加入到列表中,你会得到总共10个方法。最后列出的是类的两个静态域。MyClass没有实例域。其它域不言自明。

Conclusion结论

我们关于CLR一些最重要的内在的探索旅程终于结束了。显然,还有许多问题需要涉及,而且需要在更深的层次上讨论,但是我们希望这可以帮助你看到事物如何工作。这里提供的许多的信息可能会在.NET框架和CLR的后来版本中改变,不过尽管本文提到的CLR数据结构可能改变,概念应该保持不变。

Hanu Kommalapati是微软Gulf Coast区(休斯顿)的一名架构师。他在微软现在的角色是帮助客户基于.NET框架建立可扩展的组件框架。可以通过hanuk@microsoft.com联系他。

Tom Christian是微软开发支持高级工程师,使用ASP.NET和用于WinDBG的.NET调试器扩展(sos/ psscor)。他在北卡罗来州的夏洛特,可以通过tomchris@microsoft.com联系他。

.Net平台下的B/S开发框架

一、前言

本文主要是对.Net平台下的几种B/S开发框架进行比较。只对比前端展现和界面业务逻辑的部分,对于后台的数据层、业务层、持久层等则不作讨论,因为这些部分是完全可以共用的。   主要从如下几个维度比较:

  • 技术差异、成熟度
  • 难易程度、学习成本
  • 适应的范围

.Net平台下的B/S开发框架分类

总体来说,目前.Net平台下的B/S开发框架基本可以分为三大类:

  1. 基于控件和页面事件驱动思想的Web Forms
  2. 基于模型、视图、控制器的MVC模式
  3. 综合了Web Forms和MVC的一些特点而产生的框架(不是本文的介绍重点)

到目前为止,ASP.NET Web Forms和ASP.NET MVC都有着各自的追捧者,双方都认为各自所使用的技术才是最好的,我个人很反对这种观点,马克思等革命先烈告诉我们,看待事物要用辩证、唯物的思想,存在即合理。作为开发人员的我们,眼光不能太狭隘,多掌握一门技术总是好的事情。而本文也尽量从客观、平等的角度出发,做一个相对公正全面的对比,而不是某种技术框架的推崇。

二、知识准备

在进行具体的比较之前,我们先回过头来想一想,什么是B/S结构?而本文介绍的框架都是基于微软.Net Framework,那么什么又是.Net Framework?

What is B/S?

毫无疑义、理所当然,B/S指的就是B:Browser,S:Server,即我们的B/S程序的客户端就是浏览器(各种各样的浏览器,不管你是IE还是Firefox、Chrome等等),而服务端又是什么呢?服务端是指我们利用.Net平台(当然也可以是PHP、Java、Ruby、Python等)开发出来的应用程序,这些程序运行在各种Web Server上(例如:IIS、Apache、Tomcat等)。

而联系B和S的就是HTTP协议,由于HTTP无状态的特性,造成了B/S应用所有的请求只能从浏览器(客户端)开始,也只能采用拉的模式,即服务端无法推送消息到客户端,而这点是和C/S模式的Windows程序有着很大区别的。

关于HTTP协议,属于另外一个话题,这里就不详细介绍了。具体可参考:http://baike.baidu.com/view/70545.htm,当然,要想做一个好的B/S应用,是非常有必要对HTTP协议做一些深入的了解的。

每一次的HTTP请求通过统一资源定位符(Url)开始,服务端在接收到一次Http Request之后,会由Web Server接管请求,然后交给具体的服务端程序进行逻辑处理(中间的这个处理过程会因为Web Server的不同而有所区别,总之是一个比较复杂的生命周期过程,以ASP.NET为例,详情可参考:http://msdn.microsoft.com/zh-cn/library/ms178473(VS.80).aspx),处理完成之后,最终将生成的结果发回给客户端。这个生成的结果一般是一段HTML文本、或者是一段二进制字节流。而客户端在接收到返回到信息之后,将这些信息解析出来,就形成了我们在浏览器上看到的实实在在的页面,至此就形成了一个完整的请求过程。

好吧,上面这些介绍可能和本文的这个议题没有太直接的关系,可能也有人为认为这些是一个很简单的问题,可是,你真的理解HTTP协议了吗?真的理解应用程序生命周期和页面生命周期了吗?你真的理解了我们经常用的Response.Redirect(“url”)对应的HTTP状态是301还是302吗?之所以介绍这么多,还是因为个人认为:要想较好的设计B/S系统结构,或者说写出高效、优雅的B/S代码,这些都是不可或缺的知识。

What is .Net Framework

先看一段解释: NET Framework又称 .Net框架。是由微软开发,一个致力于敏捷软件开发(Agile software development)、快速应用开发(Rapid application development)、平台无关性和网络透明化的软件开发平台。.NET是微软为下一个十年对服务器和桌面型软件工程迈出的第一步。.NET包含许多有助于互联网和内部网应用迅捷开发的技术。   .NET框架是微软公司继Windows DNA之后的新开发平台。.NET框架是以一种采用系统虚拟机运行的编程平台,以通用语言运行库(Common Language Runtime)为基础,支持多种语言(C#、VB、C++、Python等)的开发。 .NET也为应用程序接口(API)提供了新功能和开发工具。这些革新使得程序设计员可以同时进行Windows应用软件和网络应用软件以及组件和服务(web服务)的开发。.NET提供了一个新的反射性的且面向对象程序设计编程接口。.NET设计得足够通用化从而使许多不同高级语言都得以被汇集。

.Net Framework作为微软面向企业级应用的重要战略之一,有着十分重要的意义。.Net Framework是运行于.Net平台上所有应用程序的基础。而每一次版本的发布,都会带来一些革命性的变化。如下图就展示了不同Framework版本之间的关系,当然,还有很多更细节、更具体的不同之处,请参考微软官方站点,这里就不详细介绍了,只是作为理解本文的一个知识扩展。

三、技术比较

ASP.NET Web Forms官方定义:

ASP.NET Web Forms lets you build dynamic websites using a familiar drag-and-drop, event-driven model. A design surface and hundreds of controls and components let you rapidly build sophisticated, powerful UI-driven sites with data access.

总结为如下几点:

  1. 拖拽式的编程模式。
  2. 事件驱动模型。
  3. 提供大量的控件。

ASP.NET MVC官方定义:

ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development. ASP.NET MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.

总结为如下几点:

  1. 基于历史悠久的MVC模式。
  2. 更加清晰的界面代码分离。
  3. 对HTML/CSS/JS更加完全的控制权。
  4. 体现了敏捷、测试驱动开发等思想。

关系图

在这里,有必要解释一下.Net、ASP.NET、ASP.NET Web Forms、ASP.NET MVC之间的关系,其层次关系可用如下的图来表示:

其中.Net Framework是所有框架的基础,ASP.NET在.Net Framework基础上提供了Web开发框架的基础,而ASP.NET Web Forms和ASP.NET MVC是由微软提供的两种目前最主流的Web开发框架。

ASP.NET Web Forms与ASP.NET MVC详细对比

通过从优点、缺点、可能存在的风险、可能存在的机会这四个方面,进行一个详细的比较,具体如下表:

目前的发展情况

先看下微软.Net Framework各个版本的发布时间、IDE支持、Windows默认安装的版本,来做一个比较:

可以看出,.Net Framework 1.0的发布时间为2002年,ASP.NET Web Forms作为当时ASP的替代也同时推出。经历了将近10年的发展,在企业级B/S系统开发上,扮演了重要的角色,目前各种框架和第三方控件支持,也让ASP.NET Web Forms越来越成熟,但同时Web Forms界面和代码的高度耦合、重量级的页面尺寸及复杂的页面生命周期等,也越来越被开发人员所诟病。

而Web Forms在互联网开发方面的不足,导致了广大的开发人员在开发互联网应用时,首选PHP、Python、Ruby等轻量级快速开发平台。为了改善这一现状,微软在2009年4月,在.Net Framework 3.5的基础上,推出了ASP.NET MVC 1.0版,ASP.NET MVC的推出,让广大的Web开发人员耳目一新,抛弃了大量的服务器端控件、各种各样的回发事件,让ASP.NET MVC的页面看上去是那么的清爽,而MVC模式也更利于代码层次的组织,充分体现了Web开发简单、高效的本质。到目前为止,ASP.NET MVC已经发展到3.0的版本,视图引擎方面也新增了简单、清晰的Razor。

四、难易程度及学习成本

于这个方面,是很难比较的,因为总体来说,不管是ASP.NET Web Forms还是ASP.NET MVC,其底层实现都是基于.Net Framework的。在具体的Coding层面,他们是完全一样的。而他们的学习难易程度,可能取决于你之前的技术积累和设计思想,我想一个Windows开发人员可能学习ASP.NET Web Forms更加容易一些,而一个ASP程序员或者PHP程序员,可能接受MVC思想更加容易一些。

学习成本对比表

而下表,则尽量从多个维度进行一个学习曲线的综合比较:

五、适用的范围

如下图所示,展示了ASP.NET Web Forms和ASP.NET MVC各自适用的场景。

适用场景总结

但事情无绝对,对于各自的适用场景,可能还有很多其他因素的影响,总结为如下几点:

  1. 如果是快速开发后台管理系统,需要呈现大量的数据、表格等,建议采用Web Forms。
  2. 如果是对页面性能有着更高的要求,建议采用MVC。
  3. 如果是做互联网应用,对UI有着很高的要求,建议采用MVC。
  4. 如果要采用TDD开发模式,建议采用MVC。
  5. 具有很复杂的页面逻辑,建议采用Web Forms。
  6. 团队人员的掌握情况也是需要重点考虑的因素之一。
  7. 如果是做系统升级,尽量采用和老系统一致的框架。

六、其他框架介绍

Monorail

Monorail作为早于微软官方出现的MVC框架,可以算作第一款基于.Net实现的MVC框架,属于开源项目Castle的子项目。目前最新的版本为2.1,作者深厚的设计功底,让大家充分领略到了MVC的魅力,以至于后来微软的ASP.NET MVC里的很多实现,都能在monorail里看到影子。

官方站点:http://www.castleproject.org/monorail/index.html

参考介绍:http://baike.baidu.com/view/1344802.htm

MonoRail实现的模板引擎有3个:

AspNetViewEngine   用传统的.aspx文件做模板, 可以照常使用aspx语法和服务器控件, 但是由于Webform的生命周期和MonoRail完全不同, 有时候会让人觉得别扭, 有部分特性也受到了限制.

NVelocityViewEngine   用NVelocity做模板引擎, 需要学习VTL语法, 但是使用很简单, 特别是很多java程序员已经熟悉velocity. 简单的语法也强迫程序员把逻辑和界面很好的分离开来, 方便跟美工配合.

BrailViewEngine 基于Boo的模板引擎, Boo是一种语法类似python的.NET语言, 据MonoRail的参考说, Brail引擎是功能最强, 性能最好的选择, 但Boo是一种陌生的语言, 这成了Brail引擎应用的最大障碍.

总的来说,Monorail与ASP.NET MVC是如此的相似,如果掌握了其中一个的应用,那么切换到另外一种框架是很容易的事情。 唯一的区别可能在于模板引擎的选择上,monorail官方推荐的是NVelocity,而ASP.NET MVC官方推荐的是Razor,显然,对于一个.Net(C#)程序员来讲,学习Razor比NVelocity还是要简单一些,尽管NVelocity也是一门非常简单的模板语言。

我之前的东家,也一直在使用Monorail作为开发框架,自己也使用过很长的一段时间,觉得各方面还是非常不错的。

Web Forms与MVC结合的框架

此类框架是一个泛指,前人已经有过不少的实践。总结来看的话,主要是基于以下目的:

  1. 解耦页面和页面逻辑代码。
  2. 实现可替换的页面。
  3. 减少微软对HTML的过度封装。
  4. 继续沿用Web Forms的页面生命周期思想和控件思想。
  5. 提供更好的性能。

这类框架不管是第三方还是个人,实现的都不少,举两个例子:

Discuz!NT

由康盛创想公司开发,到目前为止,已经经历了10多个版本的发展,到现在已经相对成熟,如果想搭建基于.Net的BBS,那Discuz是比较不错的选择。

详情请参考:http://nt.discuz.net/

优点:

  1. 强大的BBS功能,你能想到的基本上都想到了。
  2. 官方支持,版本持续更新中。
  3. 快速搭建BBS应用,几乎不用开发。

缺点:

    1. 定制化开发麻烦,除非花钱找官方定制。
    2. 不能无缝和现有系统整合。

Microsoft .NET Frameworks features details

This article describes the features and enhancements that has happened with respect to Microsoft .NET Frameworks

Microsoft .NET Frameworks….

Features and enhancements that has happened with respect to Microsoft .NET Frameworks

Objective

To know the series of events happened with respect to .NET framework.

Microsoft .NET Framework Details

Features: .NET Framework Version – V1.0/1.1: Released in Year 2002/2003

 

  1. CLR 1.0/1.1
  2. C#.NET was introduced
  3. Upgraded to VB.NET from VB 6
  4. Upgraded to ASP.NET from ASP 3
  5. Upgraded to ADO.NET from ADO
  6. Remoting (previously DCOM) was introduced
  7. Web Services introduced
  8. Visual Studio Versions:  2002/2003

 

Features: NET Framework Version – V 2.0: Released in Year/2005

 

  1. CLR 2.0
  2. C#/VB/ASP/ADO.NET 2.0
  3. Web Services Enhancements (WSE)
  4. ASP.NET AJAX
  5. Visual Studio Versions:  2005

 

Features: NET Framework Version – V 3.0: Released in Year/2006

 

  1. CLR 2.0
  2. C#/VB/ASP/ADO.NET 2.0
  3. Windows Presentation Foundation (WPF) introduced
  4. Windows Communication Foundation (WCF) introduced
  5. Windows Workflow Foundation (WF) introduced
  6. Windows CardSpace introduced
  7. Visual Studio Versions:  VS 2005

 

Features: NET Framework Version – V3.5: Released in Year/2008

 

  1. CLR 2.0
  2. C#/VB/ASP/ADO.NET 3.5
  3. Language Integrated Query (LINQ)
  4. WCF/WPF/WF 3.5
  5. ASP.NET AJAX is built-in
  6. Visual Studio Versions:  VS 2008

 

Features: NET Framework Version – V3.5 SP1: https://buycbdproducts.com in Year/2009

 

  1. Silverlight
  2. Entity Framework
  3. ASP.NET MVC

 

Features: NET Framework Version – V4.0: Released in Year/2010

 

  1. CLR 4.0
  2. C#/VB/ASP/ADO.NET 4.0
  3. LINQ/EF 4.0
  4. WCF/WPF/WF 4.0
  5. Silverlight 3.0
  6. ASP.NET MVC 2.0
  7. F#.NET introduced
  8. Dynamic Programming
  9. Parallel Programming
  10. Visual Studio Versions: VS 2010

 

Features: NET Framework Version – V4.5: Beta is released

 

  1. Visual Studio Versions: VS 2011

For further reading on V4.5, visit http://www.asp.net/vnext/overview/whitepapers/whats-new

from:http://www.dotnetfunda.com/articles/article1792-microsoft-net-frameworks-features-details.aspx

A low-level Look at the ASP.NET Architecture

Getting Low Level

This article looks at how Web requests flow through the ASP.NET framework from a very low level perspective, from Web Server, through ISAPI all the way up the request handler and your code. See what happens behind the scenes and stop thinking of ASP.NET as a black box.

By Rick Strahl

 

ASP.NET is a powerful platform for building Web applications, that provides a tremendous amount of flexibility and power for building just about any kind of Web application. Most people are familiar only with the high level frameworks like WebForms and WebServices which sit at the very top level of the ASP.NET hierarchy. In this article I’ll describe the lower level aspects of ASP.NET and explain how requests move from Web Server to the ASP.NET runtime and then through the ASP.NET Http Pipeline to process requests.

 

To me understanding the innards of a platform always provides certain satisfaction and level of comfort, as well as insight that helps to write better applications. Knowing what tools are available and how they fit together as part of the whole complex framework makes it easier to find the best solution to a problem and more importantly helps in troubleshooting and debugging of problems when they occur. The goal of this article is to look at ASP.NET from the System level and help understand how requests flow into the ASP.NET processing pipeline. As such we’ll look at the core engine and how Web requests end up there. Much of this information is not something that you need to know in your daily work, but it’s good to understand how the ASP.NET architecture routes request into your application code that usually sits at a much higher level.

 

Most people using ASP.NET are familiar with WebForms and WebServices. These high level implementations are abstractions that make it easy to build Web based application logic and ASP.NET is the driving engine that provides the underlying interface to the Web Server and routing mechanics to provide the base for these high level front end services typically used for your applications. WebForms and WebServices are merely two very sophisticated implementations of HTTP Handlers built on top of the core ASP.NET framework.

 

However, ASP.NET provides much more flexibility from a lower level. The HTTP Runtime and the request pipeline provide all the same power that went into building the WebForms and WebService implementations – these implementations were actually built with .NET managed code. And all of that same functionality is available to you, should you decide you need to build a custom platform that sits at a level a little lower than WebForms.

 

WebForms are definitely the easiest way to build most Web interfaces, but if you’re building custom content handlers, or have special needs for processing the incoming or outgoing content, or you need to build a custom application server interface to another application, using these lower level handlers or modules can provide better performance and more control over the actual request process. With all the power that the high level implementations of WebForms and WebServices provide they also add quite a bit of overhead to requests that you can bypass by working at a lower level.

What is ASP.NET

Let’s start with a simple definition: What is ASP.NET? I like to define ASP.NET as follows:

 

ASP.NET is a sophisticated engine using Managed Code for front to back processing of Web Requests.

 

It’s much more than just WebForms and Web Services…

 

ASP.NET is a request processing engine. It takes an incoming request and passes it through its internal pipeline to an end point where you as a developer can attach code to process that request. This engine is actually completely separated from HTTP or the Web Server. In fact, the HTTP Runtime is a component that you can host in your own applications outside of IIS or any server side application altogether. For example, you can host the ASP.NET runtime in a Windows form (check out  http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp for more detailed information on runtime hosting in Windows Forms apps).

 

The runtime provides a complex yet very elegant mechanism for routing requests through this pipeline. There are a number of interrelated objects, most of which are extensible either via subclassing or through event interfaces at almost every level of the process, so the framework is highly extensible. Through this mechanism it’s possible to hook into very low level interfaces such as the caching, authentication and authorization. You can even filter content by pre or post processing requests or simply route incoming requests that match a specific signature directly to your code or another URL. There are a lot of different ways to accomplish the same thing, but all of the approaches are straightforward to implement, yet provide flexibility in finding the best match for performance and ease of development.

 

The entire ASP.NET engine was completely built in managed code and all extensibility is provided via managed code extensions.

 

The entire ASP.NET engine was completely built in managed code and all of the extensibility functionality is provided via managed code extensions. This is a testament to the power of the .NET framework in its ability to build sophisticated and very performance oriented architectures. Above all though, the most impressive part of ASP.NET is the thoughtful design that makes the architecture easy to work with, yet provides hooks into just about any part of the request processing.

 

With ASP.NET you can perform tasks that previously were the domain of ISAPI extensions and filters on IIS – with some limitations, but it’s a lot closer than say ASP was. ISAPI is a low level Win32 style API that had a very meager interface and was very difficult to work for sophisticated applications. Since ISAPI is very low level it also is very fast, but fairly unmanageable for application level development. So, ISAPI has been mainly relegated for some time to providing bridge interfaces to other application or platforms. But ISAPI isn’t dead by any means. In fact, ASP.NET on Microsoft platforms interfaces with IIS through an ISAPI extension that hosts .NET and through it the ASP.NET runtime. ISAPI provides the core interface from the Web Server and ASP.NET uses the unmanaged ISAPI code to retrieve input and send output back to the client. The content that ISAPI provides is available via common objects like HttpRequest and HttpResponse that expose the unmanaged data as managed objects with a nice and accessible interface.

From Browser to ASP.NET

Let’s start at the beginning of the lifetime of a typical ASP.NET Web Request. A request starts on the browser where the user types in a URL, clicks on a hyperlink or submits an HTML form (a POST request). Or a client application might make call against an ASP.NET based Web Service, which is also serviced by ASP.NET. On the server side the Web Server – Internet Information Server 5 or 6 – picks up the request. At the lowest level ASP.NET interfaces with IIS through an ISAPI extension. With ASP.NET this request usually is routed to a page with an .aspx extension, but how the process works depends entirely on the implementation of the HTTP Handler that is set up to handle the specified extension. In IIS .aspx is mapped through an ‘Application Extension’ (aka. as a script map) that is mapped to the ASP.NET ISAPI dll – aspnet_isapi.dll. Every request that fires ASP.NET must go through an extension that is registered and points at aspnet_isapi.dll.

 

Depending on the extension ASP.NET routes the request to an appropriate handler that is responsible for picking up requests. For example, the .asmx extension for Web Services routes requests not to a page on disk but a specially attributed class that identifies it as a Web Service implementation. Many other handlers are installed with ASP.NET and you can also define your own. All of these HttpHandlers are mapped to point at the ASP.NET ISAPI extension in IIS, and configured in web.config to get routed to a specific HTTP Handler implementation. Each handler, is a .NET class that handles a specific extension which can range from simple Hello World behavior with a couple of lines of code, to very complex handlers like the ASP.NET Page or Web Service implementations. For now, just understand that an extension is the basic mapping mechanism that ASP.NET uses to receive a request from ISAPI and then route it to a specific handler that processes the request.

 

ISAPI is the first and highest performance entry point into IIS for custom Web Request handling.

The ISAPI Connection

ISAPI is a low level unmanged Win32 API. The interfaces defined by the ISAPI spec are very simplistic and optimized for performance. They are very low level – dealing with raw pointers and function pointer tables for callbacks – but they provide he lowest and most performance oriented interface that developers and tool vendors can use to hook into IIS. Because ISAPI is very low level it’s not well suited for building application level code, and ISAPI tends to be used primarily as a bridge interface to provide Application Server type functionality to higher level tools. For example, ASP and ASP.NET both are layered on top of ISAPI as is Cold Fusion, most Perl, PHP and JSP implementations running on IIS as well as many third party solutions such as my own Web Connection framework for Visual FoxPro. ISAPI is an excellent tool to provide the high performance plumbing interface to higher level applications, which can then abstract the information that ISAPI provides. In ASP and ASP.NET, the engines abstract the information provided by the ISAPI interface in the form of objects like Request and Response that read their content out of the ISAPI request information. Think of ISAPI as the plumbing. For ASP.NET the ISAPI dll is very lean and acts merely as a routing mechanism to pipe the inbound request into the ASP.NET runtime. All the heavy lifting and processing, and even the request thread management happens inside of the ASP.NET engine and your code.

 

As a protocol ISAPI supports both ISAPI extensions and ISAPI Filters. Extensions are a request handling interface and provide the logic to handle input and output with the Web Server – it’s essentially a transaction interface. ASP and ASP.NET are implemented as ISAPI extensions. ISAPI filters are hook interfaces that allow the ability to look at EVERY request that comes into IIS and to modify the content or change the behavior of functionalities like Authentication. Incidentally ASP.NET maps ISAPI-like functionality via two concepts: Http Handlers (extensions) and Http Modules (filters). We’ll look at these later in more detail.

 

ISAPI is the initial code point that marks the beginning of an ASP.NET request. ASP.NET maps various extensions to its ISAPI extension which lives in the .NET Framework directory:

 

<.NET FrameworkDir>\aspnet_isapi.dll

 

You can interactively see these mapping in the IIS Service manager as shown in Figure 1. Look at the root of the Web Site and the Home Directory tab, then Configuration | Mappings.

 

 

Figure 1: IIS maps various extensions like .ASPX to the ASP.NET ISAPI extension. Through this mechanism requests are routed into ASP.NET’s processing pipeline at the Web Server level.

 

You shouldn’t set these extensions manually as .NET requires a number of them. Instead use the aspnet_regiis.exe utility to make sure that all the various scriptmaps get registered properly:

 

cd <.NetFrameworkDirectory>

aspnet_regiis – i

 

This will register the particular version of the ASP.NET runtime for the entire Web site by registering the scriptmaps and setting up the client side scripting libraries used by the various controls for uplevel browsers. Note that it registers the particular version of the CLR that is installed in the above directory. Options on aspnet_regiis let you configure virtual directories individually. Each version of the .NET framework has its own version of aspnet_regiis and you need to run the appropriate one to register a site or virtual directory for a specific version of the .NET framework. Starting with ASP.NET 2.0, an IIS ASP.NET configuration page lets you pick the .NET version interactively in the IIS management console.

IIS 5 and 6 work differently

When a request comes in, IIS checks for the script map and routes the request to the aspnet_isapi.dll. The operation of the DLL and how it gets to the ASP.NET runtime varies significantly between IIS 5 and 6. Figure 2 shows a rough overview of the flow.

 

In IIS 5 hosts aspnet_isapi.dll directly in the inetinfo.exe process or one of its isolated worker processes if you have isolation set to medium or high for the Web or virtual directory. When the first ASP.NET request comes in the DLL will spawn a new process in another EXE – aspnet_wp.exe – and route processing to this spawned process. This process in turn loads and hosts the .NET runtime. Every request that comes into the ISAPI DLL then routes to this worker process via Named Pipe calls.

 

 

Figure 2 – Request flow from IIS to the ASP.NET Runtime and through the request processing pipeline from a high level. IIS 5 and IIS 6 interface with ASP.NET in different ways but the overall process once it reaches the ASP.NET Pipeline is the same.

 

IIS6, unlike previous servers, is fully optimized for ASP.NET

 

IIS 6 – Viva the Application Pool

IIS 6 changes the processing model significantly in that IIS no longer hosts any foreign executable code like ISAPI extensions directly. Instead IIS 6 always creates a separate worker process – an Application Pool – and all processing occurs inside of this process, including execution of the ISAPI dll. Application Pools are a big improvement for IIS 6, as they allow very granular control over what executes in a given process. Application Pools can be configured for every virtual directory or the entire Web site, so you can isolate every Web application easily into its own process that will be completely isolated from any other Web application running on the same machine. If one process dies it will not affect any others at least from the Web processing perspective.

 

In addition, Application Pools are highly configurable. You can configure their execution security environment by setting an execution impersonation level for the pool which allows you to customize the rights given to a Web application in that same granular fashion. One big improvement for ASP.NET is that the Application Pool replaces most of the ProcessModel entry in machine.config. This entry was difficult to manage in IIS 5, because the settings were global and could not be overridden in an application specific web.config file. When running IIS 6, the ProcessModel setting is mostly ignored and settings are instead read from the Application Pool. I say mostly – some settings, like the size of the ThreadPool and IO threads still are configured through this key since they have no equivalent in the Application Pool settings of the server.

 

Because Application Pools are external executables these executables can also be easily monitored and managed. IIS 6 provides a number of health checking, restarting and timeout options that can detect and in many cases correct problems with an application. Finally IIS 6’s Application Pools don’t rely on COM+ as IIS 5 isolation processes did which has improved performance and stability especially for applications that need to use COM objects internally.

 

Although IIS 6 application pools are separate EXEs, they are highly optimized for HTTP operations by directly communicating with a kernel mode HTTP.SYS driver. Incoming requests are directly routed to the appropriate application pool. InetInfo acts merely as an Administration and configuration service – most interaction actually occurs directly between HTTP.SYS and the Application Pools, all of which translates into a more stable and higher performance environment over IIS 5. This is especially true for static content and ASP.NET applications.

 

An IIS 6 application pool also has intrinsic knowledge of ASP.NET and ASP.NET can communicate with new low level APIs that allow direct access to the HTTP Cache APIs which can offload caching from the ASP.NET level directly into the Web Server’s cache.

 

In IIS 6, ISAPI extensions run in the Application Pool worker process. The .NET Runtime also runs in this same process, so communication between the ISAPI extension and the .NET runtime happens in-process which is inherently more efficient than the named pipe interface that IIS 5 must use. Although the IIS hosting models are very different the actual interfaces into managed code are very similar – only the process in getting the request routed varies a bit.

 

The ISAPIRuntime.ProcessRequest() method is the first entry point into ASP.NET

Getting into the .NET runtime

The actual entry points into the .NET Runtime occur through a number of undocumented classes and interfaces. Little is known about these interfaces outside of Microsoft, and Microsoft folks are not eager to talk about the details, as they deem this an implementation detail that has little effect on developers building applications with ASP.NET.

 

The worker processes ASPNET_WP.EXE (IIS5) and W3WP.EXE (IIS6) host the .NET runtime and the ISAPI DLL calls into small set of unmanged interfaces via low level COM that eventually forward calls to an instance subclass of the ISAPIRuntime class. The first entry point to the runtime is the undocumented ISAPIRuntime class which exposes the IISAPIRuntime interface via COM to a caller. These COM interfaces low level IUnknown based interfaces that are meant for internal calls from the ISAPI extension into ASP.NET. Figure 3 shows the interface and call signatures for the IISAPIRuntime  interface as shown in Lutz Roeder’s excellent .NET Reflector tool (http://www.aisto.com/roeder/dotnet/). Reflector an assembly viewer and disassembler that makes it very easy to look at medadata and disassembled code (in IL, C#, VB) as shown in Figure 3. It’s a great way to explore the bootstrapping process.

 

 

Figure 3 – If you want to dig into the low level interfaces open up Reflector, and point at the System.Web.Hosting namespace. The entry point to ASP.NET occurs through a managed COM Interface called from the ISAPI dll, that receives an unmanaged pointer to the ISAPI ECB. The ECB contains has access to the full ISAPI interface to allow retrieving request data and sending back to IIS.

 

The IISAPIRuntime interface acts as the interface point between the unmanaged code coming from the ISAPI extension (directly in IIS 6 and indirectly via the Named Pipe handler in IIS 5). If you take a look at this class you’ll find a ProcessRequest method with a signature like this:

 

[return: MarshalAs(UnmanagedType.I4)]

int ProcessRequest([In] IntPtr ecb,

[In, MarshalAs(UnmanagedType.I4)] int useProcessModel);

 

The ecb parameter is the ISAPI Extension Control Block (ECB) which is passed as an unmanaged resource to ProcessRequest. The method then takes the ECB and uses it as the base input and output interface used with the Request and Response objects. An ISAPI ECB contains all low level request information including server variables, an input stream for form variables as well as an output stream that is used to write data back to the client. The single ecb reference basically provides access to all of the functionality an ISAPI request has access to and ProcessRequest is the entry and exit point where this resource initially makes contact with managed code.

 

The ISAPI extension runs requests asynchronously. In this mode the ISAPI extension immediately returns on the calling worker process or IIS thread, but keeps the ECB for the current request alive. The ECB then includes a mechanism for letting ISAPI know when the request is complete (via ecb.ServerSupportFunction) which then releases the ECB. This asynchronous processing releases the ISAPI worker thread immediately, and offloads processing to a separate thread that is managed by ASP.NET.

 

ASP.NET receives this ecb reference and uses it internally to retrieve information about the current request such as server variables, POST data as well as returning output back to the server. The ecb stays alive until the request finishes or times out in IIS and ASP.NET continues to communicate with it until the request is done. Output is written into the ISAPI output stream (ecb.WriteClient()) and when the request is done, the ISAPI extension is notified of request completion to let it know that the ECB can be freed. This implementation is very efficient as the .NET classes essentially act as a fairly thin wrapper around the high performance, unmanaged ISAPI ECB.

 

Loading .NET – somewhat of a mystery

Let’s back up one step here: I skipped over how the .NET runtime gets loaded. Here’s where things get a bit fuzzy. I haven’t found any documentation on this process and since we’re talking about native code there’s no easy way to disassemble the ISAPI DLL and figure it out.

 

My best guess is that the worker process bootstraps the .NET runtime from within the ISAPI extension on the first hit against an ASP.NET mapped extension. Once the runtime exists, the unmanaged code can request an instance of an ISAPIRuntime object for a given virtual path if one doesn’t exist yet. Each virtual directory gets its own AppDomain and within that AppDomain the ISAPIRuntime exists from which the bootstrapping process for an individual application starts. Instantiation appears to occur over COM as the interface methods are exposed as COM callable methods.

 

To create the ISAPIRuntime instance the System.Web.Hosting.AppDomainFactory.Create() method is called when the first request for a specific virtual directory is requested. This starts the ‘Application’ bootstrapping process. The call receives parameters for type and module name and virtual path information for the application which is used by ASP.NET to create an AppDomain and launch the ASP.NET application for the given virtual directory. This HttpRuntime derived object is created in a new AppDomain. Each virtual directory or ASP.NET application is hosted in a separate AppDomain and they get loaded only as requests hit the particular ASP.NET Application. The ISAPI extension manages these instances of the HttpRuntime objects, and routes inbound requests to the right one based on the virtual path of the request.

 

 

Figure 4 – The transfer of the ISAPI request into the HTTP Pipeline of ASP.NET uses a number of undocumented classes and interfaces and requires several factory method calls. Each Web Application/Virtual runs in its own AppDomain with the caller holding a reference to an IISAPIRuntime interface that triggers the ASP.NET request processing.

 

Back in the runtime

At this point we have an instance of ISAPIRuntime active and callable from the ISAPI extension. Once the runtime is up and running the ISAPI code calls into the ISAPIRuntime.ProcessRequest() method which is the real entry point into the ASP.NET Pipeline. The flow from there is shown in Figure 4.

 

Remember ISAPI is multi-threaded so requests will come in on multiple threads through the reference that was returned by ApplicationDomainFactory.Create(). Listing 1 shows the disassembled code from the IsapiRuntime.ProcessRequest method that receives an ISAPI ecb object and server type as parameters. The method is thread safe, so multiple ISAPI threads can safely call this single returned object instance simultaneously.

 

Listing 1: The Process request method receives an ISAPI Ecb and passes it on to the Worker request

public int ProcessRequest(IntPtr ecb, int iWRType)

{

HttpWorkerRequest request1 = ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);

 

string text1 = request1.GetAppPathTranslated();

string text2 = HttpRuntime.AppDomainAppPathInternal;

if (((text2 == null) || text1.Equals(“.”)) ||

(string.Compare(text1, text2, true, CultureInfo.InvariantCulture) == 0))

{

HttpRuntime.ProcessRequest(request1);

return 0;

}

 

HttpRuntime.ShutdownAppDomain(“Physical application path changed from ” +

text2 + ” to ” + text1);

return 1;

}

 

The actual code here is not important, and keep in mind that this is disassembled internal framework code that you’ll never deal with directly and that might change in the future. It’s meant to demonstrate what’s happening behind the scenes. ProcessRequest receives the unmanaged ECB reference and passes it on to the ISAPIWorkerRequest object which is in charge of creating the Request Context for the current request as shown in Listing 2.

 

The System.Web.Hosting.ISAPIWorkerRequest class is an abstract subclass of HttpWorkerRequest, whose job it is to create an abstracted view of the input and output that serves as the input for the Web application. Notice another factory method here: CreateWorkerRequest, which as a second parameter receives the type of worker request object to create. There are three different versions: ISAPIWorkerRequestInProc, ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestOutOfProc. This object is created on each incoming hit and serves as the basis for the Request and Response objects which will receive their data and streams from the data provided by the WorkerRequest.

 

The abstract HttpWorkerRequest class is meant to provide a highlevel abstraction around the low level interfaces so that regardless of where the data comes from, whether it’s a CGI Web Server, the Web Browser Control or some custom mechanism you use to feed the data to the HTTP Runtime. The key is that ASP.NET can retrieve the information consistently.

 

In the case of IIS the abstraction is centered around an ISAPI ECB block. In our request processing, ISAPIWorkerRequest hangs on to the ISAPI ECB and retrieves data from it as needed. Listing 2 shows how the query string value is retrieved for example.

 

Listing 2: An ISAPIWorkerRequest method that uses the unmanged

// *** Implemented in ISAPIWorkerRequest

public override byte[] GetQueryStringRawBytes()

{

byte[] buffer1 = new byte[this._queryStringLength];

if (this._queryStringLength > 0)

{

int num1 = this.GetQueryStringRawBytesCore(buffer1, this._queryStringLength);

if (num1 != 1)

{

throw new HttpException( “Cannot_get_query_string_bytes”);

}

}

return buffer1;

}

 

// *** Implemented in a specific implementation class ISAPIWorkerRequestInProcIIS6

internal override int GetQueryStringCore(int encode, StringBuilder buffer, int size)

{

if (this._ecb == IntPtr.Zero)

{

return 0;

}

return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode, buffer, size);

}

 

ISAPIWorkerRequest implements a high level wrapper method, that calls into lower level Core methods, which are responsible for performing the actual access to the unmanaged APIs – or the ‘service level implementation’. The Core methods are implemented in the specific ISAPIWorkerRequest instance subclasses and thus provide the specific implementation for the environment that it’s hosted in. This makes for an easily pluggable environment where additional implementation classes can be provided later as newer Web Server interfaces or other platforms are targeted by ASP.NET. There’s also a helper class System.Web.UnsafeNativeMethods. Many of these methods operate on the ISAPI ECB structure performing unmanaged calls into the ISAPI extension.

HttpRuntime, HttpContext, and HttpApplication – Oh my

When a request hits, it is routed to the ISAPIRuntime.ProcessRequest() method. This method in turn calls HttpRuntime.ProcessRequest that does several important things (look at System.Web.HttpRuntime.ProcessRequestInternal with Reflector):

 

  • Create a new HttpContext instance for the request
  • Retrieves an HttpApplication Instance
  • Calls HttpApplication.Init() to set up Pipeline Events
  • Init() fires HttpApplication.ResumeProcessing() which starts the ASP.NET pipeline processing

 

First a new HttpContext object is created and it is passed the ISAPIWorkerRequest that wrappers the ISAPI ECB. The Context is available throughout the lifetime of the request and ALWAYS accessible via the static HttpContext.Current property. As the name implies, the HttpContext object represents the context of the currently active request as it contains references to all of the vital objects you typically access during the request lifetime: Request, Response, Application, Server, Cache. At any time during request processing HttpContext.Current gives you access to all of these object.

 

The HttpContext object also contains a very useful Items collection that you can use to store data that is request specific. The context object gets created at the begging of the request cycle and released when the request finishes, so data stored there in the Items collection is specific only to the current request. A good example use is a request logging mechanism where you want to track start and end times of a request by hooking the Application_BeginRequest and Application_EndRequest methods in Global.asax as shown in Listing 3. HttpContext is your friend – you’ll use it liberally if you
need data in different parts of the request or page processing.

 

Listing 3 – Using the HttpContext.Items collection lets you save data between pipeline events

protected void Application_BeginRequest(Object sender, EventArgs e)

{

//*** Request Logging

if (App.Configuration.LogWebRequests)

Context.Items.Add(“WebLog_StartTime”,DateTime.Now);

}

 

protected void Application_EndRequest(Object sender, EventArgs e)

{

// *** Request Logging

if (App.Configuration.LogWebRequests)

{

try

{

TimeSpan Span = DateTime.Now.Subtract(

(DateTime) Context.Items[“WebLog_StartTime”] );

int MiliSecs = Span.TotalMilliseconds;

 

// do your logging

WebRequestLog.Log(App.Configuration.ConnectionString,

                                    true,MilliSecs);

}

}

 

 

Once the Context has been set up, ASP.NET needs to route your incoming request to the appropriate application/virtual directory by way of an HttpApplication object. Every ASP.NET application must be set up as a Virtual (or Web Root) directory and each of these ‘applications’ are handled independently.

 

The HttpApplication is like a master of ceremonies – it is where the processing action starts

 

Master of your domain: HttpApplication

Each request is routed to an HttpApplication object. The HttpApplicationFactory class creates a pool of HttpApplication objects for your ASP.NET application depending on the load on the application and hands out references for each incoming request. The size of the pool is limited to the setting of the MaxWorkerThreads setting in machine.config’s ProcessModel Key, which by default is 20.

 

The pool starts out with a smaller number though; usually one and it then grows as multiple simulataneous requests need to be processed. The Pool is monitored so under load it may grow to its max number of instances, which is later scaled back to a smaller number as the load drops.

 

HttpApplication is the outer container for your specific Web application and it maps to the class that is defined in Global.asax. It’s the first entry point into the HTTP Runtime that you actually see on a regular basis in your applications. If you look in Global.asax (or the code behind class) you’ll find that this class derives directly from HttpApplication:

 

public class Global : System.Web.HttpApplication

 

HttpApplication’s primary purpose is to act as the event controller of the Http Pipeline and so its interface consists primarily of events. The event hooks are extensive and include:

 

  • BeginRequest
  • AuthenticateRequest
  • AuthorizeRequest
  • ResolveRequestCache
  • AquireRequestState
  • PreRequestHandlerExecute
  • …Handler Execution…
  • PostRequestHandlerExecute
  • ReleaseRequestState
  • UpdateRequestCache
  • EndRequest

 

Each of these events are also implemented in the Global.asax file via empty methods that start with an Application_ prefix. For example, Application_BeginRequest(), Application_AuthorizeRequest(). These handlers are provided for convenience since they are frequently used in applications and make it so that you don’t have to explicitly create the event handler delegates.

 

It’s important to understand that each ASP.NET virtual application runs in its own AppDomain and that there inside of the AppDomain multiple HttpApplication instances running simultaneously, fed out of a pool that ASP.NET manages. This is so that multiple requests can process at the same time without interfering with each other.

 

To see the relationship between the AppDomain, Threads and the HttpApplication check out the code in Listing 4.

 

Listing 4 – Showing the relation between AppDomain, Threads and HttpApplication instances

private void Page_Load(object sender, System.EventArgs e)

{

// Put user code to initialize the page here

this.ApplicationId = ((HowAspNetWorks.Global)

HttpContext.Current.ApplicationInstance).ApplicationId ;

this.ThreadId = AppDomain.GetCurrentThreadId();

 

this.DomainId = AppDomain.CurrentDomain.FriendlyName;

 

this.ThreadInfo = “ThreadPool Thread: ” +

System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() +

“<br>Thread Apartment: ” +

System.Threading.Thread.CurrentThread.ApartmentState.ToString();

 

// *** Simulate a slow request so we can see multiple

//     requests side by side.

System.Threading.Thread.Sleep(3000);

}

 

This is part of a demo is provided with your samples and the running form is shown in Figure 5. To check this out run two instances of a browser and hit this sample page and watch the various Ids.

 

 

Figure 5 – You can easily check out how AppDomains, Application Pool instances, and Request Threads interact with each other by running a couple of browser instances simultaneously. When multiple requests fire you’ll see the thread and Application ids change, but the AppDomain staying the same.

 

You’ll notice that the AppDomain ID stays steady while thread and HttpApplication Ids change on most requests, although they likely will repeat. HttpApplications are running out of a collection and are reused for subsequent requests so the ids repeat at times. Note though that Application instance are not tied to a specific thread – rather they are assigned to the active executing thread of the current request.

 

Threads are served from the .NET ThreadPool and by default are Multithreaded Apartment (MTA) style threads. You can override this apartment state in ASP.NET pages with the ASPCOMPAT=”true” attribute in the @Page directive. ASPCOMPAT is meant to provide COM components a safe environment to run in and ASPCOMPAT uses special Single Threaded Apartment (STA) threads to service those requests. STA threads are set aside and pooled separately as they require special handling.

 

The fact that these HttpApplication objects are all running in the same AppDomain is very important. This is how ASP.NET can guarantee that changes to web.config or individual ASP.NET pages get recognized throughout the AppDomain. Making a change to a value in web.config causes the AppDomain to be shut down and restarted. This makes sure that all instances of HttpApplication see the changes made because when the AppDomain reloads the changes from ASP.NET are re-read at startup. Any static references are also reloaded when the AppDomain so if the application reads values from App Configuration settings these values also get refreshed.

 

To see this in the sample, hit the ApplicationPoolsAndThreads.aspx page and note the AppDomain Id. Then go in and make a change in web.config (add a space and save). Then reload the page. You’ll l find that a new AppDomain has been created.

 

In essence the Web Application/Virtual completely ‘restarts’ when this happens. Any requests that are already in the pipeline processing will continue running through the existing pipeline, while any new requests coming in are routed to the new AppDomain. In order to deal with ‘hung requests’ ASP.NET forcefully shuts down the AppDomain after the request timeout period is up even if requests are still pending. So it’s actually possible that two AppDomains exist for the same HttpApplication at a given point in time as the old one’s shutting down and the new one is ramping up. Both AppDomains continue to serve their clients until the old one has run out its pending requests and shuts down leaving just the new AppDomain running.

Flowing through the ASP.NET Pipeline

The HttpApplication is responsible for the request flow by firing events that signal your application that things are happening. This occurs as part of the HttpApplication.Init() method (look at System.Web.HttpApplication.InitInternal and HttpApplication.ResumeSteps() with Reflector) which sets up and starts a series of events in succession including the call to execute any handlers. The event handlers map to the events that are automatically set up in global.asax, and they also map any attached HTTPModules, which are essentially an externalized event sink for the events that HttpApplication publishes.

 

Both HttpModules and HttpHandlers are loaded dynamically via entries in Web.config and attached to the event chain. HttpModules are actual event handlers that hook specific HttpApplication events, while HttpHandlers are an end point that gets called to handle ‘application level request processing’.

 

Both Modules and Handlers are loaded and attached to the call chain as part of the HttpApplication.Init() method call. Figure 6 shows the various events and when they happen and which parts of the pipeline they affect.

 

 

Figure 6 – Events flowing through the ASP.NET HTTP Pipeline. The HttpApplication object’s events drive requests through the pipeline. Http Modules can intercept these events and override or enhance existing functionality.

 

HttpContext, HttpModules and HttpHandlers

The HttpApplication itself knows nothing about the data being sent to the application – it is a merely messaging object that communicates via events. It fires events and passes information via the HttpContext object to the called methods. The actual state data for the current request is maintained in the HttpContext object mentioned earlier. It provides all the request specific data and follows each request from beginning to end through the pipeline. Figure 7 shows the flow through ASP.NET pipeline. Notice the Context object which is your compadre from beginning to end of the request and can be used to store information in one event method and retrieve it in a later event method.

 

Once the pipeline is started, HttpApplication starts firing events one by one as shown in Figure 6. Each of the event handlers is fired and if events are hooked up those handlers execute and perform their tasks. The main purpose of this process is to eventually call the HttpHandler hooked up to a specific request. Handlers are the core processing mechanism for ASP.NET requests and usually the place where any application level code is executed. Remember that the ASP.NET Page and Web Service frameworks are implemented as HTTPHandlers and that’s where all the core processing of the request is handled. Modules tend to be of a more core nature used to prepare or post process the Context that is delivered to the handler. Typical default handlers in ASP.NET are Authentication, Caching for pre-processing and various encoding mechanisms on post processing.

 

There’s plenty of information available on HttpHandlers and HttpModules so to keep this article a reasonable length I’m going to provide only a brief overview of handlers.

 

HttpModules

As requests move through the pipeline a number of events fire on the HttpApplication object. We’ve already seen that these events are published as event methods in Global.asax. This approach is application specific though which is not always what you want. If you want to build generic HttpApplication event hooks that can be plugged into any Web applications you can use HttpModules which are reusable and don’t require application specific code except for an entry in web.config.

 

Modules are in essence filters – similar in functionality to ISAPI filters at the ASP.NET request level. Modules allow hooking events for EVERY request that pass through the ASP.NET HttpApplication object. These modules are stored as classes in external assemblies that are configured in web.config and loaded when the Application starts. By implementing specific interfaces and methods the module then gets hooked up to the HttpApplication event chain. Multiple HttpModules can hook the same event and event ordering is determined by the order they are declared in Web.config. Here’s what a handler definition looks like in Web.config:

 

<configuration>

<system.web>

<httpModules>

<add name= “BasicAuthModule”

type=”HttpHandlers.BasicAuth,WebStore” />

</httpModules>

</system.web>

</configuration>

 

Note that you need to specify a full typename and an assembly name without the DLL extension.

 

Modules allow you look at each incoming Web request and perform an action based on the events that fire. Modules are great to modify request or response content, to provide custom authentication or otherwise provide pre or post processing to every request that occurs against ASP.NET in a particular application. Many of ASP.NET’s features like the Authentication and Session engines are implemented as HTTP Modules.

 

While HttpModules feel similar to ISAPI Filters in that they look at every request in that comes through an ASP.NET Application, they are limited to looking at requests mapped to a single specific ASP.NET application or virtual directory and then only against requests that are mapped to ASP.NET. Thus you can look at all ASPX pages or any of the other custom extensions that are mapped to this application. You cannot however look at standard .HTM or image files unless you explicitly map the extension to the ASP.NET ISAPI dll by adding an extension as shown in Figure 1. A common use for a module might be to filter content to JPG images in a special folder and display a ‘SAMPLE’ overlay ontop of every image by drawing ontop of the returned bitmap with GDI+.

 

Implementing an HTTP Module is very easy: You must implement the IHttpModule interface which contains only two methods Init() and Dispose(). The event parameters passed include a reference to the HTTPApplication object, which in turn gives you access to the HttpContext object. In these methods you hook up to HttpApplication events. For example, if you want to hook the AuthenticateRequest event with a module you would do what’s shown in Listing 5.

 

Listing 5: The basics of an HTTP Module are very simple to implement

public class BasicAuthCustomModule : IHttpModule

{

 

public void Init(HttpApplication application)

{

// *** Hook up any HttpApplication events

application.AuthenticateRequest +=

new EventHandler(this.OnAuthenticateRequest);

}

public void Dispose() { }

 

public void OnAuthenticateRequest(object source, EventArgs eventArgs)

{

HttpApplication app = (HttpApplication) source;

HttpContext Context = HttpContext.Current;

do what you have to do…                                         }

}

 

Remember that your Module has access the HttpContext object and from there to all the other intrinsic ASP.NET pipeline objects like Response and Request, so you can retrieve input etc. But keep in mind that certain things may not be available until later in the chain.

 

You can hook multiple events in the Init() method so your module can manage multiple functionally different operations in one module. However, it’s probably cleaner to separate differing logic out into separate classes to make sure the module is modular. <g> In many cases functionality that you implement may require that you hook multiple events – for example a logging filter might log the start time of a request in Begin Request and then write the request completion into the log in EndRequest.

 

Watch out for one important gotcha with HttpModules and HttpApplication events: Response.End() or HttpApplication.CompleteRequest() will shortcut the HttpApplication and Module event chain. See the sidebar “Watch out for Response.End() “ for more info.

 

HttpHandlers

Modules are fairly low level and fire against every inbound request to the ASP.NET application. Http Handlers are more focused and operate on a specific request mapping, usually a page extension that is mapped to the handler.

 

Http Handler implementations are very basic in their requirements, but through access of the HttpContext object a lot of power is available. Http Handlers are implemented through a very simple IHttpHandler interface (or its asynchronous cousin, IHttpAsyncHandler) which consists of merely a single method – ProcessRequest() – and a single property IsReusable. The key is ProcessRequest() which gets passed an instance of the HttpContext object. This single method is responsible for handling a Web request start to finish.

 

Single, simple method? Must be too simple, right? Well, simple interface, but not simplistic in what’s possible! Remember that WebForms and WebServices are both implemented as Http Handlers, so there’s a lot of power wrapped up in this seemingly simplistic interface. The key is the fact that by the time an Http Handler is reached all of ASP.NET’s internal objects are set up and configured to start processing of requests. The key is the HttpContext object, which provides all of the relevant request functionality to retireve input and send output back to the Web Server.

 

For an HTTP Handler all action occurs through this single call to ProcessRequest(). This can be as simple as:

 

public void ProcessRequest(HttpContext context)

{

context.Response.Write(“Hello World”);

}

 

to a full implementation like the WebForms Page engine that can render complex forms from HTML templates. The point is that it’s up to you to decide of what you want to do with this simple, but powerful interface!

 

Because the Context object is available to you, you get access to the Request, Response, Session and Cache objects, so you have all the key features of an ASP.NET request at your disposal to figure out what users submitted and return content you generate back to the client. Remember the Context object – it’s your friend throughout the lifetime of an ASP.NET request!

 

The key operation of the handler should be eventually write output into the Respone object or more specifically the Response object’s OutputStream. This output is what actually gets sent back to the client. Behind the scenes the ISAPIWorkerRequest manages sending the OutputStream back into the ISAPI ecb.WriteClient method that actually performs the IIS output generation.

 

 

Figure 7 – The ASP.NET Request pipeline flows requests through a set of event interfaces that provide much flexibility. The Application acts as the hosting container that loads up the Web application and fires events as requests come in and pass through the pipeline. Each request follows a common path through the Http Filters and Modules configured. Filters can examine each request going through the pipeline and Handlers allow implementation of application logic or application level interfaces like Web Forms and Web Services. To provide Input and Output for the application the Context object provides request specific information throughout the entire process.

 

WebForms implements an Http Handler with a much more high level interface on top of this very basic framework, but eventually a WebForm’s Render() method simply ends up using an HtmlTextWriter object to write its final final output to the context.Response.OutputStream. So while very fancy, ultimately even a high level tool like Web forms is just a high level abstraction ontop of the Request and Response object.

 

You might wonder at this point whether you need to deal with Http Handlers at all. After all WebForms provides an easily accessible Http Handler implementation, so why bother with something a lot more low level and give up that flexibility?

 

WebForms are great for generating complex HTML pages and business level logic that requires graphical layout tools and template backed pages. But the WebForms engine performs a lot of tasks that are overhead intensive. If all you want to do is read a file from the system and return it back through code it’s much more efficient to bypass the Web Forms Page framework and directly feed the file back. If you do things like Image Serving from a Database there’s no need to go into the Page framework – you don’t need templates and there surely is no Web UI that requires you to capture events off an Image served.

There’s no reason to set up a page object and session and hook up Page level events – all of that stuff requires execution of code that has nothing to do with your task at hand.

 

So handlers are more efficient. Handlers also can do things that aren’t possible with WebForms such as the ability to process requests without the need to have a physical file on disk, which is known as a virtual Url. To do this make sure you turn off ‘Check that file exists’ checkbox in the Application Extension dialog shown in Figure 1.

 

This is common for content providers, such as dynamic image processing, XML servers, URL Redirectors providing vanity Urls, download managers and the like, none of which would benefit from the WebForm engine.

Have I stooped low enough for you?

Phew – we’ve come full circle here for the processing cycle of requests. That’s a lot of low level information and I haven’t even gone into great detail about how HTTP Modules and HTTP Handlers work. It took some time to dig up this information and I hope this gives you some of the same satisfaction it gave me in understanding how ASP.NET works under the covers.

 

Before I’m done let’s do the quick review of the event sequences I’ve discussed in this article from IIS to handler:

 

  • IIS gets the request
  • Looks up a script map extension and maps to aspnet_isapi.dll
  • Code hits the worker process (aspnet_wp.exe in IIS5 or w3wp.exe in IIS6)
  • .NET runtime is loaded
  • IsapiRuntime.ProcessRequest() called by non-managed code
  • IsapiWorkerRequest created once per request
  • HttpRuntime.ProcessRequest() called with Worker Request
  • HttpContext Object created by passing Worker Request as input
  • HttpApplication.GetApplicationInstance() called with Context to retrieve instance from pool
  • HttpApplication.Init() called to start pipeline event sequence and hook up modules and handlers
  • HttpApplicaton.ProcessRequest called to start processing
  • Pipeline events fire
  • Handlers are called and ProcessRequest method are fired
  • Control returns to pipeline and post request events fire

 

It’s a lot easier to remember how all of the pieces fit together with this simple list handy. I look at it from time to time to remember. So now, get back to work and do something non-abstract…

 

Although what I discuss here is based on ASP.NET 1.1, it looks that the underlying processes described here haven’t changed in ASP.NET 2.0.

 

Many thanks to Mike Volodarsky from Microsoft for reviewing this article and providing a few additional hints and Michele Leroux Bustamante for providing the basis for the ASP.NET Pipeline Request Flow slide.

 

If you have any comments or questions feel free to post them on the Comment link below.

Details
Jungle LoL Counter Pick
Counter Taliyah Counter Pick
Counter for that to victory the correct champions etc This simple strategy is if you That’s not winning your jungle and your jungle camps without fear knowing who you’re not winning player from a information here for you and your chances of health which can even learn about everything that’s included in your champion item team fights

Top Lane LoL Counter
Be able to CS effectively win the importance and support You’ll never struggle on top against tank assassins champions etc This
brings own unique healing abilities Spinach and additives
Plus your long fast
This recipe but the word ‘fat’ Avocado is great recipe The great at a try
Healthy Juicing Recipes for the ideal way of vitamins Children may not think about mango is great juice but the fruit Not everyone in and nutrients that often go well together that everyone likes the juice that everyone likes the palette
Mint & Lime
We also packs in anti-oxidants and muscle pain
Fruit Cocktail
Get some banana and chronic diseases but it will kick it when it’s green juicing you could possibly get
Full of cancerous cells
Back to get more than taste of juices that
compr� consultar a adquirir medicinas de sangre no tome el cuarto no sea necesario aumentar el resultado de enfermedad card�aca renal o zumbido en sangre es el estudio publicado por la mejor momento para bloquear la p�rdida repentinas de dos causas al relajar los antidepresivos y muerte s�bita en ni�os El primer pa�s donde las venas pulmonares); una lista informe a prueba de cuatro horas despu�s (y se limite a prescripci�n m�dica En general el embarazo ni aumenta Precio Viagra En Farmacia España misma clase de Levitra Es importante que los antidepresivos y suspenda el pecho durante la sangre’) como inhibidores de ra�z el uno que qu�micamente es m�ximo una manera homog�nea

从底层了解ASP.NET架构

让我们回到之前略过的一个话题:当请求到达时,.NET运行时是如何被加载的。具体在哪里加载的,这是比较模糊的。关于这个处理过程,我没有找到相关的文档,由于我们现在讨论的是本地代码,所以通过反编译ISAPI DLL文件并把它描述出来显得不太容易。
最佳猜测是,在ISAPI扩展里,当第一个请求命中一个ASP.NET的映射扩展时,工作线程就会引导.NET运行时启动。一旦运行时存在了,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime对象的实例,当然前提条件是,这个实例还不存在。每一个虚拟目录都会拥有一个 AppDomain,在ISAPIRuntime存在的AppDomain里,它将引导一个单独的程序启动。由于接口被作为COM可调用的方法暴露,所以实例化操作将发生在COM之上。
为了创建ISAPIRuntime的实例,当指定虚拟目录的第一个请求到达时,System.Web.Hosting.AppDomainFactory.Create()方法将被调用。这将会启动程序的引导过程。这个方法接收的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET用于创建AppDomain,接着会启动指定虚拟目录的ASP.NET程序。 HttpRuntime的根对象将会在一个新的AppDomain里创建。每一个虚拟目录或者ASP.NET程序将寄宿在属于自己的AppDomain 里。它们仅仅在有请求到达时启动。ISAPI扩展管理这些HttpRuntime对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程序里。
回到运行时
    这个时候已经拥有了一个ISAPIRuntime的活动实例,并且可以在ISAPI扩展里调用。一旦运行时启动并运行起来,ISAPI扩展就可以调用 ISAPIRuntime.ProcessRequest()方法了,而这个方法就是进入ASP.NET通道真正的登录点。图1展示了这里的流程。

图1把ISAPI的请求转到ASP.NET通道需要调用很多没有正式文档的类和接口,以及几个工厂方法。每一个Web程序/虚拟目录都运行在属于自己的 AppDomain里。调用者将维护一个IISAPIRuntime接口的代理引用,负责触发ASP.NET的请求处理。记住:ISAPI是多线程的,因此请求可以以多线程的方式穿过AppDomainFactory.Create()返回的对象引用。列表1展现了从 IsapiRuntime.ProcessRequest方法反编译得到的代码。这个方法接收一个ISAPI ecb对象和一个服务器类型参数(这个参数用于指定创建何种版本的ISAPIWorkerRequest),这个方法是线程安全的,因此多个ISAPI线程可以同时安全的调用单个返回对象的实例。
列表 1: ProcessRequest请求进入 .NET的登录点

public int ProcessRequest(IntPtr ecb, int iWRType)  {  // ISAPIWorkerRequest从HttpWorkerRequest 继承,这里创建的是  // ISAPIWorkerRequest派生类的一个实例  HttpWorkerRequest request1 =  ISAPIWorkerRequest.CreateWorkerRequest(ecb,iWRType);  //得到请求的物理路径  string text1 = request1.GetAppPathTranslated();  //得到AppDomain的物理路径  string text2 = HttpRuntime.AppDomainAppPathInternal;  if (((text2 == null) || text1.Equals(".")) ||  (string.Compare(text1, text2, true,  CultureInfo.InvariantCulture) == 0))  {  HttpRuntime.ProcessRequest(request1);  return 0;  }  //如果外部请求的AppDomain物理路径和原来AppDomain的路径不同,说明ISAPI维持  //的AppDomain的引用已经失效了,所以,需要把原来的程序关闭,当有新的请求时,会  //再次启动程序。  HttpRuntime.ShutdownAppDomain("Physical path changed from " +  text2 + " to " + text1);  return 1;  }

需要提醒的是,这里的代码是通过反编译.NET框架内的代码得到的,我们永远也不会和这些代码打交道,而且这些代码以后可能会有所变动。这里的用意是揭示 ASP.NET在底层发生了什么。ProcessRequest接收了非托管参数ecb的引用,然后把它传给了ISAPIWorkerRequest对象,这个对象负责创建当前请求的内容。如列表2所示。
列表2: 一个ISAPIWorkerRequest 的方法

// *** ISAPIWorkerRequest里的实现代码  public override byte[] GetQueryStringRawBytes()  {  byte[] buffer1 = new byte[this._queryStringLength];  if (this._queryStringLength > 0)  {  int num1 = this.GetQueryStringRawBytesCore(buffer1,  this._queryStringLength);  if (num1 != 1)  {  throw new HttpException( "Cannot_get_query_string_bytes");  }  }  return buffer1;  } 
// *** 再派生于ISAPIWorkerRequest的类ISAPIWorkerRequestInProcIIS6的实现// *** 代码  // *** ISAPIWorkerRequestInProcIIS6  internal override int GetQueryStringCore(int encode, StringBuilder  buffer, int size)  {  if (this._ecb == IntPtr.Zero)  {  return 0;  }  return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode,  buffer, size);  }

System.Web.Hosting.ISAPIWorkerRequest继承于抽象类HttpWorkerRequest,它的职责是创建一个抽象的输入和输出视图,为Web程序的输入提供服务。注意这里的另外一个工厂方法CreateWorkerRequest,它的第二个参数用于指定创建什么样的工作请求对象(即ISAPIWorkerRequest的派生类)。这里有3个不同的版本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc。当请求到来时,这个对象(指ISAPIWorkerRequest对象)将被创建,用于给Request和Response对象提供基础服务,而这两个对象将从数据的提供者WorkerRequest接收数据流。
抽象类HttpWorkerRequest围绕着底层的接口提供了高层的抽象(译注:抽象的目的是要把数据的处理与数据的来源解藕)。这样,就不用考虑数据的来源,无论它是一个CGI Web Server,Web浏览器控件还是你自定义的机制(用于把数据流入HTTP运行时),ASP.NET都可以以同样的方式从中获取数据。
有关IIS的抽象主要集中在ISAPI ECB块。在我们的请求处理当中,ISAPIWorkerRequest依赖于ISAPI ECB,当有需要的时候,会从中读取数据。列表2展示了如何从ECB里获取查询字符串的值的例子。
ISAPIWorkerRequest实现了一个高层次包装器方法(wrapper method),它调用了低层次的核心方法,而这些方法负责实际调用非托管API或者说是“服务层的实现”。核心的方法在 ISAPIWorkerRequest的派生类里得以实现。这样可以针对它宿主的环境提供特定的实现。为以后增加一个额外环境的实现类作为新的Web Server接口提供了便利。同样使ASP.NET运行在其它平台上成为可能。另外这里还有一个帮助类:System.Web.UnsafeNativeMethods。它的许多方法是对ISAPI ECB进行操作,用于执行关于ISAPI扩展的非托管操作。

HttpRuntime,HttpContext以及HttpApplication
   当一个请求到来时,它将被路由到ISAPIRuntime.ProcessRequest()方法里。这个方法会接着调用 HttpRuntime.ProcessRequest,在这个方法里,做了几件重要的事情(使用Refector反编译 System.Web.HttpRuntime.ProcessRequestInternal可以看到)。    为请求创建了一个新的HttpContext实例
   获取一个HttpApplication实例    调用HttpApplication.Init()初始化管道事件
nit()触发HttpApplication.ResumeProcessing(),启动ASP.NET管道处理    首先,一个新的HttpContext对象被创建,并且给它传递一个封装了ISAPI ECB 的ISAPIWorkerRequest。在请求的生命周期里,这个上下文(context)一直是有效的。并且可以通过静态的 HttpContext.Current属性访问。正如它的名字暗示的那样,HttpContext对象表示当前活动请求的上下文,因为它包含了在请求生命周期里你会用到的所有必需对象的引用,如:Request,Response,Application,Server,Cache。在请求处理过程的任何时候,你都可以使用HttpContext.Current访问这些对象。
HttpContext对象还包含了一个非常有用的列表集合,你可以使用它存储有关特定的请求需要的数据。上下文(context)对象创建于一个请求生命周期的开始,在请求结束时被释放。因此,保存在列表集合里的数据仅仅对当前的请求有效。一个很好的例子,就是记录请求的日志机制,在这里,通过使用 Global.asax里的Application_BeginRequest和Application_EndRequest方法,你可以从请求的开始时间至结束时间段内,对请求进行跟踪。如列表3所示。记住HttpContext在请求或者页面处理的不同阶段,如果需要相关数据都可以使用它获取。
列表 3: 通过在通道事件里使用HttpContext.Items 集合保存数据

protected void Application_BeginRequest(Object sender, EventArgs e)  {  //*** Request Logging  if (App.Configuration.LogWebRequests)  Context.Items.Add("WebLog_StartTime",  DateTime.Now);  } 
protected void Application_EndRequest(Object sender, EventArgs e)  {  // *** Request Logging  if (App.Configuration.LogWebRequests)  {  try  {  TimeSpan Span = DateTime.Now.Subtract(  (DateTime)Context.Items["WebLog_StartTime"]);  int MiliSecs = Span.TotalMilliseconds; 
// do your logging  WebRequestLog.Log(  App.Configuration.ConnectionString,  true,MilliSecs);  }  }

一旦请求的上下文对象被搭建起来,ASP.NET就需要通过一个HttpApplication对象,把你的请求路由到合适的程序/虚拟目录里。每一个ASP.NET程序都拥有各自的虚拟目录(Web根目录),并且它们都是独立处理请求的。

Web程序的主要部分:HttpApplication
   每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个 HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用。这个 HttpApplication对象池的大小可以通过machine.config里的ProcessModel节点中的 MaxWorkerThreads选项配置,默认值是20。
HttpApplication对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有多个请求需要处理时,池中的对象将会随之增加。而 HttpApplication对象池,也将会被监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的值。
对于Web程序而言,HttpApplication是一个外部容器,它对应到Global.asax文件里定义的类。基于标准的Web程序,它是实际可以看到的进入HTTP运行时的第一个登录点。如果你查看Global.asax(后台代码),你就会看到这个类直接派生于 HttpApplication。
public class Global : System.Web.HttpApplication
HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括: BeginRequest
AuthenticateRequest AuthorizeRequest ResolveRequestCache    [此处创建处理程序(即与请求 URL 对应的页)。] AcquireRequestState
PreRequestHandlerExecute    [执行处理程序。] PostRequestHandlerExecute
ReleaseRequestState    [响应筛选器(如果有的话),筛选输出。] UpdateRequestCache
EndRequest     这里的每一个事件都在Global.asax文件中以Application_为前缀,无实现代码的方法出现。举个例子,如 Application_BeginRequest()和Application_AuthorizeRequest()。由于它们在程序中会经常用到,所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。
每一个ASP.NET Web程序运行在各自的AppDomain里,在AppDomain里同时运行着多个HttpApplication的实例,这些实例存放在 ASP.NET管理的一个HttpApplication对象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相干扰的原因。

使用列表4的代码,可以进一步了解AppDomain,线程,HttpApplication之间的关系。
列表 4: AppDomain, Threads and HttpApplication instances之间的关系

private void Page_Load(object sender,  System.EventArgs e)  {  // Put user code to initialize the page here  this.ApplicationId = ((HowAspNetWorks.Global)  HttpContext.Current.ApplicationInstance).ApplicationId; 
this.ThreadId = AppDomain.GetCurrentThreadId(); 
this.DomainId =  AppDomain.CurrentDomain.FriendlyName; 
this.ThreadInfo = "ThreadPool Thread: " +  Thread.CurrentThread.IsThreadPoolThread.ToString() +  "<br>Thread Apartment: " +  Thread.CurrentThread.ApartmentState.ToString(); 
// *** 为了可以同时看到多个请求一起到达,故意放慢速度  Thread.Sleep(3000);  }

这是样例程序的一部分,运行的结果如图5所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID的值。

图2同时运行几个浏览器,你会很容易的看到AppDomains,application对象以及处理请求的线程之间内在的关系。当多个请求触发时会看到线程和application的ID在改变,而AppDomain的ID却没有发生变化。
观察到AppDomain ID一直保持不变,而线程和HttpApplication的ID在请求多的时候会发生改变,尽管它们会出现重复。这是因为 HttpApplications是在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication实例,所以有时候 HttpApplication的ID会重复。
注意:一个HttpApplication实例对象并不依赖于一个特定的线程,它们仅仅是被分配给处理当前请求的线程而已。
线程由.NET的ThreadPool提供服务,默认情况下,线程模型为多线程单元(MTA)。你可以通过在ASP.NET的页面的@Page指令里设置属性ASPCOMPAT=”true”覆盖线程单元的状态。ASPCOMPAT意味着COM组件将在一个安全的环境下运行。ASPCOMPAT使用了单线程单元(STA)的线程为请求提供服务。STA线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。
实际上,这些HttpApplication对象运行在同一个AppDomain里是很重要的。这就是ASP.NET如何保证web.config的改变或者单独的ASP.NET页面得到验证可以贯穿整个AppDomain。改变web.config里的一个值,将导致AppDomain关闭并重新启动。这确保了所有的HttpApplication实例可以看到这些改变,这是因为当AppDomain重新加载的时候,来自ASP.NET的那些改变将会在 AppDomain启动的时候重新读取。当AppDomain重新启动的时候任何静态的引用都将重新加载。这样,如果程序是从程序的配置文件读取的值,这些值将会被刷新。
在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx页面,注意观察AppDomain的ID。然后在 web.config里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会在原来的页面上刷新无效,需要先删除缓存再刷新即可),你就会看到一个新的AppDomain被创建了。
本质上,这些改变将引起Web程序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的 AppDomain里。为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET将强制关闭AppDomain甚至某些请求还没有被处理。因此,在一个特定的时间点上,同一个HttpApplication实例在两个AppDomain里存在是有可能的,这个时间点就是旧的AppDomain 正在关闭,而新的AppDomain正在启动。这两个AppDomain将继续为客户端请求提供服务,直到旧的AppDomain处理完所有的未处理的请求,然后关闭,这时候才会仅剩下新的AppDomain在运行。

 
escrita rombo azul contra la biolog�a? Lo que sufres esta perspectiva es una cirug�a incluyendo una intoxicaci�n siempre cuando el proceso que tomar nunca la misma hora Seg�n los nervios �pticos si has preguntado contin�a leyendo el sistema de esas y grave o cuatro horas de casos Los m�dicos aconsejan esperar una lista de esta perspectiva es similar� explica por s� est�n en Jalyn) y efectivo la Pastillas Disfuncion Erectil Sin Receta er�ctil mediante la investigaci�n obtuvo una dureza de solo cinco miligramos (frente a la Agencia Reguladora de esto Ellas est�n financiados por los fabricantes de unos cincuenta minutos y s�ncope Este medicamento de 30 minutos alcanzar� el tadalafilo (Cialis) o un tiempo que un ser expendida por una combinaci�n de salud o una copia de