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的管道流程执行具体的请求处理流程。整个的流程如下图所示:

ASP.NET运行时详解 集成模式和经典模式_第1张图片

    2. IIS 7 模式

    IIS 7和之前的版本区别比较大,IIS7直接把ASP.NET的运行管道流程集成到了IIS上。先看下IIS7从接收请求到请求处理完毕的流程图:

ASP.NET运行时详解 集成模式和经典模式_第2张图片

    在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>>。什么意思呢?我们先看下面的图:

ASP.NET运行时详解 集成模式和经典模式_第3张图片

    通过上面的图表可看出,每个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应用作哪些扩展。

    如果本篇内容对大家有帮助,请关注博主。如果觉得不好,也欢迎拍砖。你们的反馈就是博主的动力!下篇内容,敬请期待!

你可能感兴趣的:(ASP.NET运行时详解 集成模式和经典模式)