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

ModuleContainers

    通過上面的圖表可看出,每個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應用作哪些擴展。

    如果本篇內容對大家有幫助,請關注博主。如果覺得不好,也歡迎拍磚。你們的反饋就是博主的動力!下篇內容,敬請期待!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM