標籤:注意 obj 平台 tor 隔離 pos 方法 mvc 單元
目前我主要使用.Net MVC架構進行網頁建立,資料庫是MSSQL Server。所以,我就用.NET MVC架構的web頁面周期來說明頁面的生命週期,但是我覺著其他MVC架構也是大同小異的。
本文主要分兩個部分
一、.NET MVC的網頁生命週期
二、普通ASP.NET的網頁生命週期
一、.NET MVC的網頁生命週期
ASP.NET MVC請求從開始到結束的每一個過程,在瀏覽器輸入URL並敲擊斷行符號來請求一個ASP.Net MVC網站的頁面之後發生的任何事情,都是頁面的生命週期的一部分。
為什麼需要關心這些?有兩個原因。首先是因為ASP.NET MVC是一個擴充性非常強的架構。例如,我們可以插入不同的ViewEngine來控制網站內容呈現的方式。我們還可以定義控制器產生和分配到某個請求的方式。因為我想發掘任何ASP.NET MVC頁面請求的擴充點,所以我要來探究請求過程中的一些步驟。
其次,如果你對測試驅動開發佷感興趣,當為控制器寫單元測試時,我們就必須理解控制器的依賴項。在寫測試的時候,我們需要使用諸如Typemock Isolator或Rhino Mocks的Mock架構來類比某些對象。如果不瞭解頁面請求生命週期就不能進行有效類比。
生命週期步驟概覽
當我們對ASP.NET MVC網站發出一個請求的時候,會發生5個主要步驟:
步驟1:建立RouteTable
當ASP.NET應用程式第一次啟動的時候才會發生第一步。RouteTable把URL映射到Handler。
步驟2:UrlRoutingModule攔截請求
第二步在我們發起請求的時候發生。UrlRoutingModule攔截了每一個請求並且建立和執行合適的Handler。
步驟3:執行MvcHandler
MvcHandler建立了控制器,並且把控制器傳入ControllerContext,然後執行控制器。
步驟4:執行控制器
控制器檢測要執行的控制器方法,構建參數列表並且執行方法。
步驟5:調用RenderView方法
大多數情況下,控制器方法調用RenderView()來把內容呈現回瀏覽器。Controller.RenderView()方法把這個工作委託給某個ViewEngine來做。
現在讓我們來詳細研究每一個步驟:
步驟1:建立RouteTable
當我們請求普通ASP.NET應用程式頁面的時候,對於每一個頁面請求都會在磁碟上有這樣一個頁面。例如,如果我們請求一個叫做SomePage.aspx的頁面,在WEB伺服器上就會有一個叫做SomePage.aspx的頁面。如果沒有的話,會得到一個錯誤。
從技術角度說,ASP.NET頁面代表一個類,並且不是普通類。ASP.NET頁面是一個Handler。換句話說,ASP.NET頁面實現了IhttpHandler介面並且有一個ProcessRequest()方法用於在請求頁面的時候接受請求。ProcessRequest()方法負責產生內容並把它發回瀏覽器。
因此,普通ASP.NET應用程式的工作方式佷簡單明了。我們請求頁面,頁面請求對應磁碟上的某個頁面,這個頁面執行ProcessRequest()方法並把內容發回瀏覽器。
ASP.NET MVC應用程式不是以這種方式工作的。當我們請求一個ASP.NET MVC應用程式的頁面時,在磁碟上不存在對應請求的頁面。而是,請求被路由轉到一個叫做控制器的類上。控制器負責產生內容並把它發回瀏覽器。
當我們寫普通ASP.NET應用程式的時候,會建立很多頁面。在URL和頁面之間總是一一對應進行映射。每一個頁面請求對應相應的頁面。
相反,當我們建立ASP.NET MVC應用程式的時候,建立的是一批控制器。使用控制器的優勢是可以在URL和頁面之間可以有多對一的映射。例如,所有如下的URL都可以映射到相同的控制器上。
http://MySite/Products/1http://MySite/Products/2http://MySite/Products/3
這些URL映射到一個控制器上,通過從URL中提取產品ID來顯示正確的產品。這種控制器方式比傳統的ASP.NET方式更靈活。控制器方式可以產品更顯而易見的URL。
那麼,某個頁面請求是怎麼路由到某個控制器上的呢?ASP.NET MVC應用程式有一個叫做路由表(Route Table)的東西。路由表映射某個URL到某個控制器上。一個應用程式有一個並且只會有一個路由表。路由表在Global.asax檔案中建立。
應用程式的路由表由RouteTable.Routes的靜態屬性表示。這個屬性工作表示了路由對象的集合。我們在應用程式初次開機時為路由表增加兩個路由對象(Application_Start()方法在第一次請求網站頁面的時候被調用一次)。
路由對象負責把URL映射到Handler。我們可以建立了兩個路由對象。這2個對象都把URL映射到MvcRouteHandler。第一個路由映射任何符合{controller}/{action}/{id}模式的URL到MvcRouteHandler。第二個路由映射某個URL Default.aspx到MvcRouteHandler。
順便說一下,這種新的路由構架可以脫離ASP.NET MVC獨立使用。Global.asax檔案對應URL到MvcRouteHandler。然而,我們可以選擇把URL路由到不同類型的Handler上。這裡說的路由構架包含在一個叫做System.Web.Routing.dll的獨立程式集中。我們可以脫離MVC使用路由。
步驟2:UrlRoutingModule攔截請求
當我們對ASP.NET MVC應用程式發起請求的時候,請求會被UrlRoutingModule HTTP Module攔截。HTTP Module是特殊類型的類,它參與每一次頁面請求。例如,傳統ASP.NET包含了FormsAuthenticationModule HTTP Module用來使用表單驗證實現頁面訪問安全性。
UrlRoutingModule攔截請求後做的第一件事情就是封裝當前的HttpContext為HttpContextWrapper2對象。HttpContextWrapper2類和派生自HttpContextBase的普通HttpContext類不同。建立的HttpContext的封裝可以使使用諸如Typemock Isolator或Rhino Mocks的Mock對象框進行類比變得更簡單。
接著,Module把封裝後的HttpContext傳給在之前步驟中建立的RouteTable。HttpContext包含了URL、表單參數、查詢字串參數以及和當前請求關聯的cookie。如果在當前請求和路由表中的路由對象之間能找到匹配,就會返迴路由對象。
如果UrlRoutingModule成功擷取了RouteData對象,Module然後就會建立表示當前HttpContext和RouteData的RouteContext對象。Module然後執行個體化基於RouteTable的新HttpHandler,並且把RouteContext傳給Handler的建構函式。
對於ASP.NET MVC應用程式,從RouteTable返回的Handler總是MvcHandler(MvcRouteHandler返回MvcHandler)。只要UrlRoutingModule匹配當前請求到路由表中的路由,就會執行個體化帶有當前RouteContext的MvcHandler。
Module進行的最後一步就是把MvcHandler設定為當前的HTPP Handler。ASP.NET應用程式自動調用當前HTTP Handler的ProcessRequest()方法然後轉入下一步。
步驟3:執行MvcHandler
在之前的步驟中,表示某個RouteContext的MvcHandler被設定作為當前的HTTP Handler。ASP.NET應用程總是會發起一系列的事件,包括Star、BeginRequest、PostResolveRequestCache、 PostMapRequestHandler、PreRequestHandlerExecute和EndRequest事件(非常多的應用程式事件——對於完整列表,請查閱Visual Studio 文檔中的HttpApplication類)。
之前內容中描述的所有東西都在PostResolveRequestCache和PostMapRequestHandler中發生。當前HTTP Handler的ProcessRequest()方法在PreRequestHandlerExecute事件之後被調用。
當之前內容中建立的MvcHandler對象的ProcessRequest()被調用的時候,會建立一個新的控制器。控制器由ControllerFactory建立。由於我們可以建立自己的ControllerFactory,所以這又是一個可擴充點。預設的ControllerFactory名字相當合適,叫做DefaultControllerFactory。
RequestContext以及控制器的名字被傳入ControllerFactory.CreateController()方法來獲得一個控制器。然後,從RequestContext和控制器構造ControllerContext對象。最後,調用控制器類的Execute()方法。在調用Execute()方法的時候會給方法傳入ControllerContext。
步驟4:執行控制器
Execute()方法首先建立TempData對象(在Ruby On Rails中叫做Flash對象)。TempData可以用於儲存下次請求必須的臨時資料(TempData和工作階段狀態差不多,不長期佔用記憶體)。
接著,Execute()方法構建請求的參數列表。這些參數從請求參數中提取,將會被作為方法的參數。參數會被傳入執行的控制器方法。
Execute()通過對控制器類進行反射來找到控制器的方法。控制器類是我們寫的。Execute()方法找到了我們控制器類中的方法後就執行它。Execute()方法不會執行被裝飾NonAction特性的方法。
至此,就進入了自己應用程式的代碼。
步驟5:調用RenderView方法
通常,我們的控制器方法最後會調用RenderView()或RedirectToAction()方法。RenderView()方法負責把視圖(頁面)呈現給瀏覽器。
當我們調用控制器RenderView()方法的時候,調用會委託給當前ViewEngine的RenderView()方法。ViewEngine是另外一個擴充點。預設的ViewEngine是WebFormViewEngine。然而,我們可以使用諸如Nhaml的其它ViewEngine。
WebForm的ViewEngine.RenderView()方法建立了一個叫做ViewLocator的類來尋找視圖。然後,它使用BuildManager來建立ViewPage類的執行個體。然後,如果頁面有ViewData就會設定ViewData。最後,ViewPage 的RenderView()方法被調用。
ViewPage類從System.Web.UI.Page基類(和用於傳統ASP.NET的頁面一樣)派生。RenderView()方法做的最後一個工作就是調用頁面類的ProcessRequest()。調用視圖的ProcessRequest()產生內容的方式和普通ASP.NET頁面產生內容的方式一致。
可擴充點
ASP.NET MVC生命週期在設計的時候包含了很多可擴充點。我們可以自訂通過插入自訂類或覆蓋既有類來自訂架構的行為。下面是這些擴充點的概要:
路由對象:當我們建立路由表的時候,調用RouteCollection.Add()方法來增加新的路由對象。Add()方法接受了RouteBase對象。我們可以通過派生RouteBase基類來實現自己的路由對象。
MvcRouteHandler :當建立MVC應用程式的時候,我們把URL映射到MvcRouteHandler對象上。然而,我們可以把URL映射到實現IRouteHandler介面的任何類上。路由類的建構函式接受任何實現IRouteHandler介面的對象。
MvcRouteHandler.GetHttpHandler():MvcRouteHandler 類的GetHttpHandler()方法是virtual方法。預設情況下,MvcRouteHandler返回MvcHandler。如果願意的話,我們可以覆蓋GetHttpHandler()方法來返回不同的Handler。
ControllerFactory:我們可以通過System.Web.MVC.ControllerBuilder.Current.SetControllerFactory()方法指定一個自訂類來建立自訂的控制器工廠。控制器工廠負責為某個控制器名和RequestContext返回控制器。
控制器:我們可以通過實現Icontroller介面來實現自訂控制器。這個介面只有一個Execute(ControllerContext controllerContext)方法。
ViewEngine:我們可以為控制器指定自訂的ViewEngine。通過為公用的Controller.ViewEngine屬性指定ViewEngine來把ViewEngine指定給控制器。ViewEngine必須實現IviewEngine介面,介面只有一個方法:RenderView(ViewContext viewContext)。
ViewLocator :ViewLocator把視圖名映射到實際視圖檔案上。我們可以通過WebFormViewEngine.ViewLocator的屬性來執行自訂的ViewLocator。
二、普通ASP.NET的網頁生命週期
大體上,ASP.NET請求的處理流程分為如所示的兩步。使用者發送一個請求到IIS伺服器時:
(1)ASP.NET會建立一個能夠處理請求的環境。換句話說,它會建立一個包含請求、響應以及內容物件的應用程式物件來處理這個請求。
(2)一旦ASP.NET環境被建立,使用者請求就會通過由modules(管道)、handlers(處理常式)和page objects(頁面對象)觸發的一系列事件進行處理。簡而言之,我們暫且將此步湊稱為MHPM(Module、Handler、Page和Module Event)。
ASP.NET環境的建立
第一步:使用者請求到達IIS後,IIS首先會檢查哪一個ISAPI擴充能夠處理這個請求,這會取決於檔案的尾碼名。例如:如果請求的是一個‘.aspx‘的頁面,那麼就會被傳遞到‘aspnet_isapi.dll‘來進行處理。
第二步:如果這是該網站的首次請求,那麼一個稱為‘ApplicationManager‘的類會首先建立一個該網站可以啟動並執行應用程式定義域(App Domain)。正如我們所知,應用程式定義域隔離部署在同一台IIS伺服器上的兩個不同的Web應用程式。因此,即使其中一個應用程式定義域出現了錯誤,也不會影響其他應用程式定義域的正常運作。
(.NET平台下,程式集並沒有直接載入進進程中。.NET可執行程式承載在進程的一個邏輯分區中,術語稱應用程式定義域(簡稱AppDomain)。應用程式定義域是.NET引入的一個新概念,它比進程所佔用的資源要少,可以被看作是一個輕量級的進程。在一個進程中可以包含多個應用程式定義域,一個應用程式定義域可以裝載一個可執行程式(*.exe)或者多個程式集(*.dll)。這樣可以使應用程式定義域之間實現深度隔離,所以:即使進程中的某個應用程式定義域出現錯誤,也不會影響其他應用程式定義域的正常運作。)
第三步:在新建立的應用程式定義域中,會建立ASP.NET的宿主環境,也就是HttpRuntime對象。一旦宿主環境被建立完成,ASP.NET最核心的對象如HttpContext、HttpRequest和HttpResponse對象都會被建立好。
第四步:一旦所有核心的ASP.NET對象被建立好,HttpApplication對象就會隨之被建立來服務這個請求。如果你的系統中存在一個global.asax檔案,那麼這個global.asax檔案的對象也會被建立。但是,需要注意的是你的global.asax需要繼承自HttpApplication類。
注意:在一個ASP.NET頁面第一次附加到網站,一個HttpApplication執行個體便隨之產生。為了最大化得提高處理效能,HttpApplication的執行個體將會被複用以處理多個請求。
Global.asax 檔案(也稱作 ASP.NET 應用程式檔案)是可選檔案,包含用於響應 ASP.NET 或 HttpModule 引發的應用程式層級事件的代碼。(換句話說,我們可以自訂後面我們所要介紹的一些事件,因為請求處理流程會經曆後面的10多個事件,我們可以寫代碼來自訂其中的一些事件,加一些我們想做的商務邏輯操作,比如:URL重寫、身分識別驗證、圖片浮水印等等。)如果不定義該檔案,ASP.NET 頁架構假設您未定義任何應用程式或會話事件處理常式。
第五步:此時HttpApplication對象將會被分配給一系列的ASP.NET核心對象來處理請求的頁面。
第六步:這時,HttpApplication開始通過HTTP管道事件、處理常式(Handlers)和頁面事件來處理請求了。也就是說:它會觸發 MHPM 中的事件來處理請求。
.NET MVC頁面生命週期及傳統ASP.NET頁面周期