ASP.NET运行时详解 集成模式和经典模式
遗留问题
在《ASP.NET运行时详解 生命周期入口分析》中遗留两个问题,包括Application的InitInternal方法执行细节、IIS6和II7经典模式请求管道管理类ApplicationStepManager和IIS7请求管道管理类PipelineStepManager的实现细节。这两个问题贯穿了整个ASP.NET运行过程。所以,要把ASP.NET运行过程了解清楚,这两个问题不得不解决。
为了大家更容易切入该篇的内容,我们先回顾下这两个问题:1. Application的InitInternal方法
上一篇“初始化Application对象”章节中,HttpApplicationFactory工厂创建Application对象的方法GetApplicationInstance中有这么一段代码:private HttpApplication GetNormalApplicationInstance(HttpContext context) { //… if (state == null) { state = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType); using (new ApplicationImpersonationContext()) { state.InitInternal(context, this._state, this._eventHandlerMethods); } } return state; }代码中HttpApplication实体对象state调用InitInternal方法执行初始化操作。
2. StepManager的两个实现类ApplicationStepManager和PipelineStepManager
上一篇“执行请求过程”章节中,有介绍HttpApplication类的ResumeSteps方法,代码如下:
private void ResumeSteps(Exception error) { this._stepManager.ResumeSteps(error); }
ResumeSteps方法调用StepManager的ResumeSteps方法执行ASP.NET运行的管道步骤。但在最初时,ASP.NET运行管道肯定是空的。那么,又由谁来构建ASP.NET运行管道?这里就提到第一个问题中的InitInternal方法。
预备知识
在解决上节的两个问题之前,我们先得了解下IIS 6以及IIS 7经典模式和IIS 7在执行请求过程中的一些区别,ASP.NET 5是兼容了这两种模式。这也就是为什么StepManager有两个实现类,ApplicationStepManager对应IIS6以及IIS 7经典模式,而PipelineStepManager对应IIS 7的集成模式。下面分别介绍这两种模式。
1. IIS 6以及IIS 7经典模式
早期的IIS版本中,IIS接收到一个请求时先判断请求类型,如果是静态文件直接由IIS处理;如果是一个ASP.NET请求类型,IIS把请求发送给IIS的扩展接口ASP.NET ISAPI DLL。ISAPI相当于ASP.NET应用的容器,当他接收到ASP.NET类型的请求后,启动ASP.NET的管道流程执行具体的请求处理流程。整个的流程如下图所示:
2. IIS 7 模式
IIS 7和之前的版本区别比较大,IIS7直接把ASP.NET的运行管道流程集成到了IIS上。先看下IIS7从接收请求到请求处理完毕的流程图:
在IIS7中,ASP.NET请求处理管道Pipeline直接覆盖了IIS本身的管道Pipeline,IIS整个流程按照ASP.ENT管道流程执行,例如BeginRequest、AuthenticateRequest、…、EndRequest。而不是通过插件扩展(ISAPI)形式,不同的内容(jpg、html、php等)通过不同的插件来执行。
IIS7的优势体现在哪里?开发人员可以实现自定义的HttpModule或者HttpHandler,然后直接集成到IIS上,例如,我可以自定义一个JpgHttpHandler,然后通过IIS的Handler部署方式,把JpgHttpHandler部署到IIS上,处理所有的请求格式为*.jpg的请求。
至于怎样把自定义的HttpModule或者HttpHandler部署到IIS上并处理指定格式的请求。这里我就不详细介绍了,感兴趣的同学可在评论反馈下,我根据情况,再单独开一篇详细介绍IIS7。ASP.NET执行管道初始化
前面提到了Application的InitInternal方法,那么InitInternal方法中具体包括哪些操作?看看经过裁剪后的代码:
internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) { using (new DisposableHttpContextWrapper(context)) { if (HttpRuntime.UseIntegratedPipeline) { this.InitIntegratedModules(); goto Label_006B; } this.InitModules(); Label_006B: if (handlers != null) { this.HookupEventHandlersForApplicationAndModules(handlers); } } if (HttpRuntime.UseIntegratedPipeline && (this._context != null)) { this._context.HideRequestResponse = false; } this._resumeStepsWaitCallback = new WaitCallback(this.ResumeStepsWaitCallback); if (HttpRuntime.UseIntegratedPipeline) { this._stepManager = new PipelineStepManager(this); } else { this._stepManager = new ApplicationStepManager(this); } this._stepManager.BuildSteps(this._resumeStepsWaitCallback); }
代码中,HookupEventHandlersForApplicationAndModules方法接收了一个handlers参数,handler中存放了Application的所有Application事件,所有HookupEventHandlersForApplicationAndModules的工作就是遍历handlers事件,分别执行。我们通过代码可看到有两处都用到HttpRuntime的UseIntegratedPipeline作为判断条件。UseIntegratedPipeline用来判断是否是集成模式,也就是我们上面提到的IIS7模式。
第一处判断,如果是集成模式就调用InitIntegratedModules方法初始化Module配置;如果是经典模式就调用InitModules方法初始化Module配置。
第二处判断,如果是集成模式,_stepManager实例化为PipelineStepManager类型;如果是经典模式,_stepManager实例化为ApplicationStepManager类型。
最后一段代码调用BuildSteps方法构建执行步骤到ASP.NET运行管道事件上。
经过上面的分析,不管是集成模式还是经典模式,执行管道的初始化都包含两个步骤:初始化Module配置、构建执行步骤。接下来,我们分别按照集成模式和经典模式介绍这两个步骤。管道初始化-集成模式
1. 初始化Module配置
InitIntegratedModules方法的实现很简单,两行代码:
private void InitIntegratedModules() { this._moduleCollection = this.BuildIntegratedModuleCollection(_moduleConfigInfo); this.InitModulesCommon(); }
第一行代码调用BuildIntegratedModuleCollection方法初始化_moduleCollection集合,这个方法遍历module配置集合,把每一个module配置保存到ModulesEntry对象,ModulesEntry存放了module名称和module的类型,它还提供了一个Create方法创建Module实体类。_moduleCollection存储了Module的名称和实体对象。先留一个问题:module配置集合_moduleConfigInfo的数据是从哪里来的?
第二行代码调用InitModulesCommon方法初始化_moduleCollection集合中的每一个module。InitModulesCommon方法代码如下:private void InitModulesCommon() { int count = this._moduleCollection.Count; for (int i = 0; i < count; i++) { t his._currentModuleCollectionKey = this._moduleCollection.GetKey(i); this._moduleCollection[i].Init(this); } this.InitAppLevelCulture(); }
最主要的一行代码是_moduleCollection[i].Init(this),我们知道_moduleCollection存储了Module的实体对象。所以_moduleCollection[i]就是一个IHttpModule的实体对象。Init方法大家就该熟悉了,它把Module(例如,UrlAuthorizationModule、FormsAuthenticationModule等)的事件注册到执行管道流程事件(BeginRequest、ResolveRequestCache、EndRequest等)中。
初始化Module完成后,Application中保存了_moduleIndexMap和ModuleContainers两个集合,_moduleIndexMap存储结构是HashTable<ModuleName,Index>,而ModuleContainers存储结构为Container<Index, List<Event>>。什么意思呢?我们先看下面的图:通过上面的图表可看出,每个Module对应了一个管道列表,每个管道对应一个Events集合。到目前为止,我们已经为后面的“执行请求管道”提供了数据基础。现在我们再回看之前遗留的一个问题:module配置集合_moduleConfigInfo的数据是从哪里来的?
我们之前说过IIS7已经集成了ASP.NET的运行管道,它把对应的Module和Handler添加到了自身的配置文件中,在IIS 7管理工具“管理->配置管理项”里,我们可以看到具体的配置信息。 Application类中提供了一个GetModuleCollection方法,它正是从IIS的配置文件中加载了所有的Module。
总结下集成模式的管道初始化:Application先从IIS配置读取Module信息,然后把Module对应的事件存放到ModuleContainers集合。2. 构建执行步骤
集成模式的StepManager的实现类是PipelineStepManager。之前介绍Application的InitInternal方法,它的最后一行代码调用了PipelineStepManager对象的BuildSteps方法。BuildSteps方法代码如下:
internal override void BuildSteps(WaitCallback stepCallback) { HttpApplication app = base._application; HttpApplication.IExecutionStep step = new HttpApplication.MaterializeHandlerExecutionStep(app); app.AddEventMapping("ManagedPipelineHandler", RequestNotification.MapRequestHandler, false, step); app.AddEventMapping("ManagedPipelineHandler", RequestNotification.ExecuteRequestHandler, false, app.CreateImplicitAsyncPreloadExecutionStep()); HttpApplication.IExecutionStep step2 = new HttpApplication.CallHandlerExecutionStep(app); app.AddEventMapping("ManagedPipelineHandler", RequestNotification.ExecuteRequestHandler, false, step2); HttpApplication.IExecutionStep step3 = new HttpApplication.TransitionToWebSocketsExecutionStep(app); app.AddEventMapping("ManagedPipelineHandler", RequestNotification.EndRequest, true, step3); HttpApplication.IExecutionStep step4 = new HttpApplication.CallFilterExecutionStep(app); app.AddEventMapping("AspNetFilterModule", RequestNotification.UpdateRequestCache, false, step4); app.AddEventMapping("AspNetFilterModule", RequestNotification.LogRequest, false, step4); this._resumeStepsWaitCallback = stepCallback; }
BuildSteps方法添加几个执行步骤(IExecuteStep)到ModuleContainer中指定Module的某个管道的事件集合中。我们拿第三行代码举例,在添加之前创建一个step,然后找到Application的ModuleContainers集合中ModuleName为ManagedPipelineHandler的管道,最后根据传入的事件类型RequestNotification.ExecuteRequestHandler把step添加到对应管道的事件列表中。
添加这些步骤有什么用?最主要的两个作用即是重定向(Remap)IHttpHandler、执行IHttpHandler的ProcessRequest方法。至于详细的说明,我们再下一个篇幅中再讲解。管道初始化-经典模式
1. 初始化Module配置
从上面我们已经了解到集成模式读取Module配置是从IIS读取,那经典模式又是从哪里读取配置?下看下经典模式初始化Module配置代码:
private void InitModules() { HttpModuleCollection modules = RuntimeConfig.GetAppConfig().HttpModules.CreateModules(); HttpModuleCollection other = this.CreateDynamicModules(); modules.AppendCollection(other); this._moduleCollection = modules; this.InitModulesCommon(); }
通过第一行代码,不难看出Module配置是从RuntimeConfig中读取,也就是从machine.config配置中读取,而不是IIS。第二行代码是通过动态的方式提供Module,一般没有使用。最后一行代码执行InitModulesCommon方法,在上一节我们可看到该方法的定义。
简而言之,经典模式的Module是从machine.config中读取。2. 构建执行步骤
经典模式的StepManager的实现类是ApplicationStepManager。之前介绍Application的InitInternal方法,它的最后一行代码调用了ApplicationStepManager对象的BuildSteps方法。BuildSteps方法的代码有点多,但必须得全部粘出来:
internal override void BuildSteps(WaitCallback stepCallback) { ArrayList steps = new ArrayList(); HttpApplication app = base._application; bool flag = false; UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings; flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0); steps.Add(new HttpApplication.ValidateRequestExecutionStep(app)); steps.Add(new HttpApplication.ValidatePathExecutionStep(app)); if (flag) { steps.Add(new HttpApplication.UrlMappingsExecutionStep(app)); } app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps); steps.Add(new HttpApplication.MapHandlerExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps); app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps); steps.Add(app.CreateImplicitAsyncPreloadExecutionStep()); steps.Add(new HttpApplication.CallHandlerExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps); app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps); steps.Add(new HttpApplication.CallFilterExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps); this._endRequestStepIndex = steps.Count; app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps); steps.Add(new HttpApplication.NoopExecutionStep()); this._execSteps = new HttpApplication.IExecutionStep[steps.Count]; steps.CopyTo(this._execSteps); this._resumeStepsWaitCallback = stepCallback; }
通过上面的代码猜测,估计BuildSteps方法直接创建了执行管道的所有步骤。至于对不对,我们来分析分析代码。第一行创建了一个steps集合,它的集合成员有两种添加方式:一种是steps.Add,另外一种是app.CreateEventExecutionSteps。下面分别举例分析:
1. steps.Add
例如steps.Add(new HttpApplication.ValidateRequestExecutionStep(app)),直接给steps添加一个校验request请求的ValidateRequestExecutionStep步骤。
2. app.CreateEventExecutionStep
例如app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps),这里调用了Application的CreateEventExecutionSteps方法,代码如下:private void CreateEventExecutionSteps(object eventIndex, ArrayList steps) { AsyncAppEventHandler handler = this.AsyncEvents[eventIndex]; if (handler != null) { handler.CreateExecutionSteps(this, steps); } EventHandler handler2 = (EventHandler)this.Events[eventIndex]; if (handler2 != null) { Delegate[] invocationList = handler2.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { steps.Add(new SyncEventExecutionStep(this, (EventHandler)invocationList[i])); } } }
从代码可以看出,Application把EventBeginRequest注册过的异步事件(AsyncEvents)、同步事件Events中的事件遍历,然后每一个事件创建一个同步或者异步的ExecutionStep步骤。最终我们的steps就相当于是一个过程化的链表,一个个事件串在一起按部就班的执行。
这些步骤中也包括了想MapHandlerExecutionStep(定向Handler)、CallHandlerExecutionStep(执行Handler)步骤。对比集成模式,集成模式把管道Pipeline按结构化的形式存储在ModuleContainers中,而经典模式按过程化的形式直接把所有事件排列成一个列表。但不管是集成模式还是经典模式,最终执行管道的内容是一样的。只不过集成模式更加具有扩展性。上面有很多的ExecutionStep,每一个Step的作用是什么,由于篇幅问题,我在下一篇在作介绍。总结
这一篇的内容主要围绕“遗留问题”章节中的Application的InitInternal方法、StepManager的实现两个问题做执行管道的介绍。由于涉及到IIS7的集成模式,所有我在“预备知识”中简单的介绍了集成模式和经典模式的区别。然后分别分析集成模式、经典模式中Module的初始化以及执行管道的构建的过程。
本篇内容中也多次提到Module,Module也就是我们经常看到的IHttpModule,它可以在初始化的时候往我们的请求执行管道中添加自定义事件,在这些事件中我们可以做任何想做的事件,例如校验Request信息、验证身份、缓存处理、重定向IHttpHandler等。
剩下的内容包括:经典模式和集成模式下管道具体是怎样执行的、管道中各个步骤执行的内容、页面的生命周期。这些内容我将会在下一篇中做详细介绍。另外,还有提到IIS 7集成模式,如果大家感兴趣我也可另起炉灶,分享下IIS 7到底发生了什么变化,以及我们可以使用IIS 7为我们的WEB应用作哪些扩展。
如果本篇内容对大家有帮助,请关注博主。如果觉得不好,也欢迎拍砖。你们的反馈就是博主的动力!下篇内容,敬请期待!