HttpRuntime,HttpContext和HttpApplication
当一个请求到来时,它被路由到ISAPIRuntime.ProcessRequest()方法.这个方法调用HttpRuntime.ProcessRequest方法,它作一些重要的事情(用Reflector查看System.Web.HttpRuntime.ProcessRequestInternal方法):
- 为请求创建一个新的HttpContext实例
- 获取一个HttpApplication实例
- 调用HttpApplication.Init()方法来设置管道的事件
- Init()方法触发开始ASP.NET管道处理的HttpApplication.ResumeProcessing()方法
首先一个新的HttpContext对象被创建并用来传递ISAPIWorkerRequest(ISAPI ECB的包装器).这个上下文在整个请求的生命周期总都是可用的并总可以通过静态属性HttpContext.Currect来访问.正像名字所暗示的那样,HttpContext对象代表了当前活动请求的上下文因为他包含了在请求生命周期中所有典型的你需要访问的重要对象:Request,Response,Application,Server,Cache.在请求处理的任何时候HttpContext.Current给你访问所有这些的能力.
HttpContext对象也包含一个非常有用的Items集合,你可以用它来保存针对特定请求的数据.上下文对象在请求周期的开始时被创建,在请求结束时被释放,所有在Items集合中保存的数据只在这个特定的请求中可用.一个很好的使用的例子是请求日志机制,当你通过想通过在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根目录)而且每个”应用程序”是被单独的处理的.
HttpApplication类似仪式的主人-它是处理动作开始的地方
域的主人:HttpApplication
每个请求都被路由到一个HttpApplication对象上.HttpApplicationFactory类根据应用程序的负载为你的ASP.NET应用创建一个HttpApplication对象池并为每个请求分发HttpApplication对象的引用.对象池的大小受machine.config文件中ProcessModel键中的MaxWorkerThreads设置限制,默认是20个(译注:此处可能有误,根据Reflector反编译的代码,池的大小应该是100个,如果池大小小于100,HttpApplicationFactory会创建满100个,但是考虑到会有多个线程同时创建HttpApplication的情况,实际情况下有可能会超过100个).
对象池以一个更小的数字开始;通常是一个然后增长到和同时发生的需要被处理的请求数量一样.对象池被监视,这样在大负载下它可能会增加到最大的实例数量,当负载降低时会变回一个更小的数字.
HttpApplication是你的Web程序的外部包装器,而且它被映射到在Global.asax里面定义的类上.它是进入HttpRuntime的第一个入口点.如果你查看Global.asax(或者对应的代码类)你会发现这个类直接继承自HttpApplication:
public class
Global : System.Web.HttpApplication
HttpApplication的主要职责是作为Http管道的事件控制器,所以它的接口主要包含的是事件.事件挂接是非常广泛的,包括以下这些:
l
BeginRequest
l
AuthenticateRequest
l
AuthorizeRequest
l
ResolveRequestCache
l
AquireRequestState
l
PreRequestHandlerExecute
l
…Handler
Execution…
l
PostRequestHandlerExecute
l
ReleaseRequestState
l
UpdateRequestCache
l
EndRequest
每个事件在Global.assx文件中以Application_前缀开头的空事件作为实现.例如, Application_BeginRequest(), Application_AuthorizeRequest()..这些处理器为了便于使用而提供因为它们是在程序中经常被使用的,这样你就不用显式的创建这些事件处理委托了.
理解每个ASP.NET虚拟目录在它自己的应用程序域中运行,而且在应用程序域中有多个从ASP.NET管理的池中返回的HttpApplication实例同时运行,是非常重要的.这是多个请求可以被同时处理而不互相妨碍的原因.
查看列表4来获得应用程序域,线程和HttpApplication之间的关系.
列表4-显示应用程序域,线程和HttpApplication实例之间的关系
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);
}
这是随sample提供的demo的一部分,运行的结果在图5中显示.运行两个浏览器,打开这个演示页面可以看到不同的ID.
图5-你可以通过同时运行多个浏览器来简单的查看应用程序域,应用程序池实例和请求线程是如何交互的.当多个请求同时发起,你可以看到线程ID和应用程序ID变化了,但是应用程序域还是同一个.
你可能注意到在大多数请求上,当线程和HttpApplication ID变化时应用程序域ID却保持不变,虽然它们也可能重复(指线程和HttpApplication
ID).HttpApplication是从一个集合中取出,在随后到来的请求中可以被复用的,所以它的ID有时是会重复的.注意Application实例并不和特定的线程绑定-确切的说它们是被指定给当前请求的活动线程.
线程是由.NET的线程池管理的,默认是多线程套间(MTA)线程.你可以在ASP.NET的页面上通过指定@Page指令的属性ASPCOMPAT=”true”来覆盖套间属性.ASPCOMPAT意味着为COM组件提供一个安全的执行环境,