【輝郎】ASP.NET MVC深度接觸:ASP.NET MVC請求生命週期

iDotNetSpace發表於2008-06-06
這篇博文的目的旨在詳細描述ASP.NET MVC請求從開始到結束的每一個過程。我希望能理解在瀏覽器輸入URL並敲擊回車來請求一個ASP.NET MVC網站的頁面之後發生的任何事情。

為什麼需要關心這些?有兩個原因。首先是因為ASP.NET MVC是一個擴充套件性非常強的框架。例如,我們可以插入不同的ViewEngine來控制網站內容呈現的方式。我們還可以定義控制器生成和分配到某個請求的 方式。因為我想發掘任何ASP.NET MVC頁面請求的擴充套件點,所以我要來探究請求過程中的一些步驟。

其次,我對測試驅動開發佷感興趣。要為控制器寫單元測試,我們就必須理解控制器的依賴項。在寫測試的時候,我需要使用諸如Typemock Isolator或Rhino Mocks的Mock框架來模擬某些物件。如果不瞭解頁面請求生命週期就不能進行有效的模擬。

兩個警告

首先,要給大家兩個警告。

第一個警告:我在ASP.NET MVC Preview2對外發布之後寫了此文。ASP.NET MVC框架在Beta前還會變化很大。因此,我在此文中描述的任何東西都會在幾個月後不再有效。因此,如果你在2008年5月之後讀此文的話,不要相信任 何你讀到的東西。

第二,此文不是ASP.NET MVC的概覽。我只是枯草地介紹了ASP.NET MVC請求的生命週期。好了,別說我沒有提醒你。

生命週期步驟概覽

當我們對ASP.NET MVC網站發出一個請求的時候,會發生5個主要步驟:

1、步驟1——建立RouteTable

當ASP.NET應用程式第一次啟動的時候才會發生第一步。RouteTable把URL對映到Handler。

2、步驟2——UrlRoutingModule攔截請求

第二步在我們發起請求的時候發生。UrlRoutingModule攔截了每一個請求並且建立和執行合適的Handler。

3、步驟3——執行MvcHandler

MvcHandler建立了控制器,並且把控制器傳入ControllerContext,然後執行控制器。

4、步驟4——執行控制器

控制器檢測要執行的控制器方法,構建引數列表並且執行方法。

5、步驟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/1

http://MySite/Products/2

http://MySite/Products/3

這些URL對映到一個控制器上,通過從URL中提取產品ID來顯示正確的產品。這種控制器方式比傳統的ASP.NET方式更靈活。控制器方式可以產品更顯而易見的URL。

那麼,某個頁面請求是怎麼路由到某個控制器上的呢?ASP.NET MVC應用程式有一個叫做路由表(Route Table)的東西。路由表對映某個URL到某個控制器上。

一個應用程式有一個並且只會有一個路由表。路由表在Global.asax檔案中建立。清單1包含了在使用Visual Studio新建ASP.NET MVC Web應用程式時預設的Global.asax檔案。

清單 1 – Global.asax

1: using System;

2: using System.Collections.Generic;

3: using System.Linq;

4: using System.Web;

5: using System.Web.Mvc;

6: using System.Web.Routing;

7:

8: namespace TestMVCArch

9: {

10: public class GlobalApplication : System.Web.HttpApplication

11: {

12: public static void RegisterRoutes(RouteCollection routes)

13: {

14: // Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable

15: // automatic support on IIS6 and IIS7 classic mode

16:

17: routes.Add(new Route("{controller}/{action}/{id}", new MvcRouteHandler())

18: {

19: Defaults = new RouteValueDictionary(new { action = "Index", id = "" }),

20: });

21:

22: routes.Add(new Route("Default.aspx", new MvcRouteHandler())

23: {

24: Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),

25: });

26: }

27:

28: protected void Application_Start(object sender, EventArgs e)

29: {

30: RegisterRoutes(RouteTable.Routes);

31: }

32: }

33: }

應用程式的路由表由RouteTable.Routes的靜態屬性表示。這個屬性表示了路由物件的集合。在清單1列出的Global.asax檔案中,我 們在應用程式首次啟動時為路由表增加兩個路由物件(Application_Start()方法在第一次請求網站頁面的時候被呼叫一次)。

路由物件負責把URL對映到Handler。在清單1中,我們建立了兩個路由物件。這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 2008文件中的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生命週期在設計的時候包含了很多可擴充套件點。我們可以自定義通過插入自定義類或覆蓋既有類來自定義框架的行為。下面是這些擴充套件點的概要:

1、路由物件 —— 當我們建立路由表的時候,呼叫RouteCollection.Add()方法來增加新的路由物件。Add()方法接受了RouteBase物件。我們可以通過派生RouteBase基類來實現自己的路由物件。

2、 MvcRouteHandler —— 當建立MVC應用程式的時候,我們把URL對映到MvcRouteHandler物件上。然而,我們可以把URL對映到實現IRouteHandler接 口的任何類上。路由類的建構函式接受任何實現IRouteHandler介面的物件。

3、 MvcRouteHandler.GetHttpHandler() —— MvcRouteHandler 類的GetHttpHandler()方法是virtual方法。預設情況下,MvcRouteHandler返回MvcHandler。如果願意的話, 我們可以覆蓋GetHttpHandler()方法來返回不同的Handler。

4、ControllerFactory ——我們可以通過System.Web.MVC.ControllerBuilder.Current.SetControllerFactory()方 法指定一個自定義類來建立自定義的控制器工廠。控制器工廠負責為某個控制器名和RequestContext返回控制器。

5、控制器 —— 我們可以通過實現Icontroller介面來實現自定義控制器。這個介面只有一個Execute(ControllerContext controllerContext)方法。

6、 ViewEngine —— 我們可以為控制器指定自定義的ViewEngine。通過為公共的Controller.ViewEngine屬性指定ViewEngine來把 ViewEngine指定給控制器。ViewEngine必須實現IviewEngine介面,介面只有一個方法:RenderView (ViewContext viewContext)。

7、ViewLocator —— ViewLocator把檢視名對映到實際檢視檔案上。我們可以通過WebFormViewEngine.ViewLocator的屬性來執行自定義的ViewLocator。

如果你發現任何我遺漏的擴充套件點,請為留下你的評論(留在原作者文章下),我會更新此文。

結束語

本文的目的是描述ASP.NET MVC請求從開始到結束的整個生命週期。我們研究了處理ASP.NET MVC請求的5個過程:建立RouteTable,UrlRoutingModule攔截請求,生成控制器,執行行為以及呈現檢視。最後,我介紹了 ASP.NET MVC框架的可擴充套件點。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-332662/,如需轉載,請註明出處,否則將追究法律責任。

相關文章