說起ASP.NET的生命周期,網上有很多的介紹。之前也看了些這方面的博客,但我感覺很多程序猿像我一樣,看的時候似乎明白,一段時間過后又忘了。所以,最近Heavi花了一段時間研究ASP.NET的源代碼,通過代碼層面來掌握ASP.NET的生命周期。分析的代碼版本是ASP.NET 5。
生命周期入口HttpRunTime
在HttpRunTime中Http請求的入口處理流程比較復雜,說簡單一點就是為后面的處理流程提供兩個參數:IIS7WorkerRequest和HttpContext。這兩個參數生成后直接調用HttpRuntime.ProcessRequestNotification(wr, httpContext)方法,執行Http請求的整個流程。
所以,我們從ProcessRequestNotificationPrivate方法開始分析。代碼如下:private RequestNotificationStatus ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) { RequestNotificationStatus pending = RequestNotificationStatus.Pending; if (context.NeedToInitializeApp()) { context.Response.InitResponseWriter(); applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); HttpApplication application = applicationInstance as HttpApplication; if (application != null) { application.AssignContext(context); } } wr.SynchronizeVariables(context); if (context.ApplicationInstance != null) { if (context.ApplicationInstance.BeginProcessRequestNotification(context, this._requestNotificationCompletionCallback).CompletedSynchronously) { pending = RequestNotificationStatus.Continue; } } else if (applicationInstance != null) { applicationInstance.ProcessRequest(context); pending = RequestNotificationStatus.FinishRequest; } else { pending = RequestNotificationStatus.Continue; } if (pending != RequestNotificationStatus.Pending) { this.FinishRequestNotification(wr, context, ref pending); } return pending; }這個方法里邊包含的主要流程有:
1. 初始化運行環境:EnsureFirstRequestInit。確認應用是否是第一次請求,如果是需要初始化運行環境,包括初始化Configuration、應用程序池、加載Bin下的Dll等。
2. 初始化Response對象:context.Response.InitResponseWriter。
3. 初始化Application對象:HttpApplicationFactory.GetApplicationInstance(context)。
4. 執行請求過程:context.ApplicationInstance.BeginProcessRequestNotification。
5. 完成請求通知:FinishRequestNotification。
對於上面的主要流程,我用流程圖表示可能更直觀些。流程圖如下所示:知道了上面的5個主要步驟,接下來就詳細的介紹每個步驟執行的具體內容。
初始化運行環境
通過執行EnsureFirstRequestInit方法來初始化我們整個應用的運行環境,那具體初始化了哪些操作?我們先查看下方法的源代碼:
private void EnsureFirstRequestInit(HttpContext context) { if (this._beforeFirstRequest) { lock (this) { if (this._beforeFirstRequest) { this._firstRequestStartTime = DateTime.UtcNow; this.FirstRequestInit(context); this._beforeFirstRequest = false; context.FirstRequest = true; } } } }
代碼就那么寥寥幾行,很明顯的看出源代碼里邊最主要的一行就是:this.FirstRequestInit(context)。也就是說初始化運行環境實際是在這個方法里邊執行的。我把FirstRequestInit方法里邊的代碼簡單歸納了一下,下面是比較核心的代碼流程:
1. 初始化WEB配置:InitHttpConfiguration()。
我們經常從ConfigurationManager的AppSettings和ConnectionStrings讀取配置信息,這些配置信息就是InitHttpConfiguration方法從IIS環境和WEB環境下讀取到內存提供給程序使用。
2. 臨時文件夾寫權限判斷:CheckAccessToTempDirectory()。
簡單理解就是整個WEB環境在運行時需要保存某些文件到臨時文件夾里邊,所以必須保證我們的WEB環境有臨時文件夾的寫權限。
3. 初始化請求隊列:InitRequestQueue()。
按照服務器CPU環境自動或手動配置請求進程隊列的限制,如果需要手動配置可在Web.config中配置ProcessModelSection部分。
4. 健康監測系統運行環境:HealthMonitoringManager.StartHealthMonitoringHeartbeat()。
啟動一個計時器,定時檢測編譯錯誤、未處理異常、登錄失敗等信息,並記錄到Windows事件日志、SQL Server數據庫以及Trace輸出窗口等。我們可以通過在Web.config中配置HealthMonitoringSection模塊來初始化檢測機制。
5. 約束IIS文件夾:RestrictIISFolders(context)。
執行ISAPI接口的函數(具體是執行什么操作,目前還不清楚)。
6. 加載Bin下面的程序集:PreloadAssembliesFromBin()。
加載Bin以及Bin下的子目錄的所有程序集。
7. 請求標頭檢查,防止注入攻擊:this.InitHeaderEncoding()。運行環境初始化過程流程圖如下:
初始化Response對象
該步驟就一個操作,初始化我們常用到的Reponse.Output輸出對象。
初始化Application對象
執行HttpApplicationFactory工廠的GetApplicationInstance方法初始化Application對象。先把代碼貼出來:
internal static IHttpHandler GetApplicationInstance(HttpContext context) { //…省略其他代碼 _theApplicationFactory.EnsureInited(); _theApplicationFactory.EnsureAppStartCalled(context); return _theApplicationFactory.GetNormalApplicationInstance(context); }
代碼第一行執行工廠的EnsureInited方法,獲取global.asax文件所在路徑,然后編譯它。編譯后啟動檢測,查看global.asax文件是否有更新,如果有更新WEB應用會被停止。
代碼第二行執行工廠方法EnsureAppStartCalled(context),創建特別的HttpApplication實例,觸發ApplicationOnStart事件,執行global_asax中的Application_Start事件,新建的特別的HttpApplication實例在處理完事件后就被回收。
代碼第三行執行工廠方法GetNormalApplicationInstance(context),創建我們在WEB應用中經常用到的Application對象。既然是我們經常用到的Application對象,那么GetNormalApplicationInstance方法里邊應該不僅僅是簡單的創建步驟。我們先看下GetNormalApplicationInstance方法的代碼:private HttpApplication GetNormalApplicationInstance(HttpContext context) { HttpApplication state = null; lock (this._freeList) { if (this._numFreeAppInstances > 0) { state = (HttpApplication)this._freeList.Pop(); this._numFreeAppInstances--; if (this._numFreeAppInstances < this._minFreeAppInstances) { this._minFreeAppInstances = this._numFreeAppInstances; } } } if (state == null) { state = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType); using (new ApplicationImpersonationContext()) { state.InitInternal(context, this._state, this._eventHandlerMethods); } } return state; }
這段代碼我們必須得分析下,這里包含了我們經常說的“應用程序池“,體現在代碼里邊就是_freeList集合,在獲取Application對象時,先從_freeList查找空閑的Application對象,如果有就直接返回一個空閑對象;如果沒有則執行HttpRuntime.CreateNonPublicInstance(this._theApplicationType)方法創建一個Application對象,創建之后調用InitInternal方法執行初始化操作。
InitInternal方法比較重要,它初始化了我們的HttpModules,並且構建了Http請求的20多個管道執行步驟。InitInternal方法執行的具體內容比較龐大,我會單獨分一個章節來介紹。但要提出一點,該方法為我們創建了具體的管道管理對象StepManager,該對象在下一個”執行請求過程“步驟中會用到。
執行請求過程
初始化操作執行完了就該處理我們的請求了,Application對象的BeginProcessRequestNotification方法包含了請求的處理過程。看下代碼:
internal IAsyncResult BeginProcessRequestNotification(HttpContext context, AsyncCallback cb) { if (this._context == null) { this.AssignContext(context); } context.CurrentModuleEventIndex = -1; HttpAsyncResult result = new HttpAsyncResult(cb, context); context.NotificationContext.AsyncResult = result; this.ResumeSteps(null); return result; }
方法創建HttpAsyncResult對象,保存了我們的回調事件。等ResumeSteps方法執行完后再回調HttpAsyncResult的事件。接下來看看ResumeSteps方法的具體內容。代碼比較簡單,如下所示:
private void ResumeSteps(Exception error) { this._stepManager.ResumeSteps(error); }代碼里邊有一個_stepManager對象,在上一個步驟“初始化Application對象”中我們有提到StepManager,StepManager實體對象也是在那個步驟中被創建的。ResumeSteps方法啟動了Http請求執行管道的20多個步驟。那么,StepManager到底包含哪些內容?我們查看下他的定義:
internal abstract class StepManager { internal abstract void BuildSteps(WaitCallback stepCallback); internal abstract void InitRequest(); internal abstract void ResumeSteps(Exception error); }StepManager是一個抽象類,定義了三個抽象方法,BuildSteps(構建步驟)和ResumeSteps(執行步驟)我們已經知道了,InitRequest用來初始化請求信息。StepManager總共有兩個實現類:ApplicationStepManager和PipelineStepManager。ApplicationStepManager是IIS6以及IIS7經典模式執行步驟管理對象,PipelineStepManager是IIS7的執行步驟管理
對象。這兩個對象會在下一章節詳細介紹。完成請求通知
FinishRequestNotification主要做善后工作,代碼如下:
private void FinishRequestNotification(IIS7WorkerRequest wr, HttpContext context, ref RequestNotificationStatus status) { HttpApplication applicationInstance = context.ApplicationInstance; context.ReportRuntimeErrorIfExists(ref status); IntPtr requestContext = wr.RequestContext; bool sendHeaders = UnsafeIISMethods.MgdIsLastNotification(requestContext, status); try { context.Response.UpdateNativeResponse(sendHeaders); } catch (Exception exception) { } if (sendHeaders) { context.FinishPipelineRequest(); } if (status != RequestNotificationStatus.Pending) { PipelineRuntime.DisposeHandler(context, requestContext, status); } }
從代碼可以看出FinishRequestNotification方法記錄運行時異常,調用context.FinishPipelineRequest方法完成整個請求、釋放Request、Response以及Application對象。
總結
通過上面幾個步驟的分析,請求的入口是在HttpRuntime。第一次Http請求發生后,HttpRuntime初始化整個WEB環境,然后通過HttpApplication的工廠類HttpApplicationFactory創建HttpApplication。HttpApplication實體對象通過StepManager的BuildSteps方法構建請求管道(Pipeline)步驟,然后通過ResumeSteps方法執行管道所有的事件。
下一章節主要介紹管Pipeline步驟的構建和執行細節以及頁面的生命周期,敬請期待!