標籤:
一、網址路由
1.1 比對通過瀏覽器傳來的HTTP請求
用戶端對ASP.NET網站發出請求時,能通過R偶湯尼蓋找到適當的HttpHandler來處理網頁,大致的流程
如果HttpHandler是由MvcHandler來處理,那麼,此時就會進入MVC的執行生命週期,並且會找到適當的Controller與Action來對其進行處理,並將資訊反饋給用戶端。
1.2 將適當的網址返回瀏覽器
網址路由的另一個用途是決定MVC 應該輸出什麼樣的網址並將其返回瀏覽器,跳轉地址或在View中顯示超連結時,都需要參考網址路由的定義,因為這樣才能動態決定MVC輸出網址應該是什麼。
1.3 預設網址路由
Global.asax已經定義了兩個預設的網址路由,參考圖如下:
① 所有ASP.NET Web應用程式執行的入口都是HttpApplication的Application_Start()事件,所有的MVC Routing都會在此定義。其中,RoutTable.Routes是一個公開的靜態對象,用於儲存所有Routing的規則集(RouteCollection類)。
② 預設RegisterRoutes()方法中的IgnoreRoute()輔助方法用於定義不需要通過Routing處理的網址。
③ "{resource}"代表一個名為"resource"的Route Valueg運算式,但其實這裡取任何名字都可以,它只是代表一個變數空間(PlaceHolder類),總之就是代表一個位置,用於放置一個用不到的變數。
④ "{*pathInfo}"代表一個名稱為"pathInfo"的RouteValue運算式,名稱前面的星號(*)的意思是"CatchAll"(抓到全部)。這個名為"pathInfo"的RouteValue運算式的值是完整的路徑資訊(Path Info)中扣除在③中比對到的剩餘的網址。例如:網址是"/TEST.axd/a/b/c/d",則"{PathInfo}"的值為"a/b/c/d",如果沒有加上星號,"{PathInfo}"的值應為"a"。其實在這裡取任何名字都可以,因為它只代表一個變數的位置。
⑤ MapRoute()是最常用來定義Routing規則的輔助方法。
⑥ 定義Route的名稱,在此為"Default"。
⑦ 定義網址格式和每個網址段落(PathSegment)的RouteValue運算式名稱。
TIP : 該網址不能以斜線(/)開頭。
⑧ 定義各RouteValue運算式的預設值,當網址路由比對不到HTTP請求網址時,就會改以預設值替代。
二、HTTP請求的URL如何對應網址路由
由於預設定義了兩個網址路由,按照ASP.NET Routing的規則,當HTTP提出請求後,URL會進行網址路由的比對,而且是由上而下地一條一條比對,直到比對到符合HTTP請求的網址為止。
2.1 網址路由範例
下面舉例,弄清URL和Routing之間更多概念上的聯絡。
[1]. http://localhost/Trace.axd/a/b/c/d/e
TIP :所有的網址都是從"http://localhost/"之後開始比對的!
(1)比對routes.IgnoreRoute命名空間的"{resource}.axd/{*pathInfo}"網址格式。
(2)"{resource}.axd"比對到"Trace.axd",因此繼續比對下一個RouteValue運算式。
(3)比對"{*pathInfo}",得到"a/b/c/d/e"。
(4)因為所有的RouteValue運算式都比對成功,所以該HTTP請求會由此網址路由提供服務。
該網址使用routes.IgnoreRoute命名空間進行處理,即,MVC為忽略此請求,改為ASP.NET架構本身繼續進行處理。
[2] http://localhost/Trade.axd
(1)比對routes.IgnoreRoute命名空間的"{resource}.axd/{*pathInfo}"網址格式。
(2)"{resource}.axd"比對到"Trace.axd",因此繼續比對下一個RouteValue運算式。
(3)比對"{*pathInfo}",由於請求的部分已經沒有資料,所以理論上不會比對到任何結果。但由於"{*pathInfo}"屬於CallAll,此文法會比對到包括Null 字元串在內的全部內容,所以這個部分也算比對成功,只是"{*pathInfo}"為空白而已。
(4)因為所有的RouteValue運算式都比對成功,所以該HTTP請求會由此網址路由提供服務。
該網址使用routes.IgnoreRoute命名空間進行處理,即,MVC為忽略此請求,改為ASP.NET架構本身繼續進行處理。
[3] http://localhost/Member/Detail?id=123
(1)比對routes.IgnoreRoute命名空間的"{resource}.axd/{*pathInfo}"網址格式。
(2)比對請求URL的的一部分,即"Member",由於沒有比對到".axd",所以比對失敗。
(3)跳轉到routes.MapRoute命名空間的"{controller}/{action}/{id}"網址格式。
(4)比對請求URL的第一部分,即"Member",並且比對到{controller}參數。
(5)比對請求URL的第二部分,即"Detail",並且比對到"{action}"參數。
(6)接下來的"?id=123"就不算是網址的一部分了,所以它不會被算進RouteValue運算式中,因此不會在對此比對了。
(7)"{id}"部分因為沒有比對到,所以會讀取預設值,也就是"UrlParameter.Optional"部分。由於存在預設值,所以也算比對成功。
(8)因為所有的RouteValue運算式都比對成功,所以該HTTP請求會由此網址路由提供服務。
該網址使用routes.MapRoute命名空間進行處理,並調過MvcHandler將值賦予適當的Controller和Action程式。在這裡會對應MemberController的Detail動作。
TIP :在"URL及參數"位置出現的所有路由參數都是必要的參數,必須完全符合必讀規則才能比對成功;如果比對失敗,就會調至下一條網址路由規則繼續比對。
2.2 為網址路由加上限制條件
MapRoute()是最常用來定義Routing規則的輔助方法,它有許多應用方式(重載)。一個常用的應用方式為"樣式比對規則(Regex)+限制條件"。
1 context.MapRoute(2 3 "Order_default",4 "Order/{controller}/{action}/{id}",5 new { action = "Index", id = UrlParameter.Optional },6 new { id = @"\d+" }7 );
我們在MapRoute()輔助方法中設定了4個參數,這些參數指定了一個匿名對象,其中的id屬性就是我們比對{id}路由值的限制條件,其限制條件是用Regex(Regular Expression)來表示的。"\id+"代表比對到的{id}路由值時必須為數字才算比對成功,而這就是限制條件。
若網址為"http://localhost/Order/Member/Index/123ABC.",由於此對到的{id}路由值不符合限制條件,所以這個網址就算比對失敗,接著會自動跳到下一個網址路由規則繼續比對。
TIP : 這裡定義的Regex預設是完全比對。如果你定義的樣式為"\id+‘,事實上,在比對時會轉換為"^\d+$"。
三、網址路由如何在MVC中產生網址。
URL是如何比對網址路由是網址路由的一個功能,另一個主要功能是在Controller或View中依據網址路由的定義產生適當的網址。下面介紹如何使用RouteTable.Routes.GetVirtualPath命名空間取得動態網址。
先用預設的MVC項目模組進行測試。開啟網址http://localhost/Home/About,此請求所得到的路由值
欄位 |
值 |
controller |
Home |
action |
About |
id |
UrlParameter.Optional |
在"/Views/Home/About.aspx"頁面中添加以下程式碼。
1 <%=2 RouteTable.Routes.GetVirtualPath(3 Request.RequestContext,4 new RouteValueDictionary(new { page=1})5 ).VirtualPath 6 %>
可以看到:在RouteTable.Routes.GetVirtualPath命名空間中,第一個參數為Request.RequestContext,在會輸入當前的請求資訊,包括RouteValue、QueryString和其他完整的請求;第二個參數中多輸入了一個RouteValueDictionary對象,並插入了一個{page}路由值。因此,在擷取網址路由之前會先合并出一組路由值,如表所示:
欄位 |
值 |
Action |
About |
Id |
UrlParameter.Optional |
Page |
1 |
最後,MVC會使用這組新的路由值由上而下——比對路由表(RouteTable)中所有的路由規則,已得到最適合的路由規則,併產生適當的網址。
拿以下路由規則來說,其中定義了3個路由參數,而我們得到的4個路由值中有3個路由值完全符合定義,所以,此網址路由會被選中,並且MVC會以此路由定義好的格式產生網址。
1 routes.MapRoute( 2 "Default", // 路由名稱 3 "{controller}/{action}/{id}", // 帶有參數的 URL 4 new 5 { // 參數預設值 6 controller = "Home", 7 action = "Index", 8 id = UrlParameter.Optional 9 }10 );
由於{page}參數並非網址路由參數之一,因此,建立的{page}參數就被替換成了QueryString參數,輸出結果如下。
/Home/About?page=1
再舉一個複雜的例子。第一條路由規則定義如下:
1 routes.MapRoute( 2 "Member", // 路由名稱 3 "Member/{action}/{page}", // 帶有參數的 URL 4 new // 參數預設值 5 { 6 controller = "MemberCenter", 7 action = "List" 8 }, 9 new10 {11 action = @"Index|List|Detail",12 page = @"\d+"13 }14 );15 routes.MapRoute(16 "Default", // 路由名稱17 "{controller}/{action}/{id}", // 帶有參數的 URL18 new19 { // 參數預設值 20 controller = "Home",21 action = "Index",22 id = UrlParameter.Optional23 }24 );
在"/Views/Home/About.aspx‘頁面中添加以下程式碼。
1 <%= 2 RouteTable.Routes.GetVirtualPath( 3 Request.RequestContext, 4 new RouteValueDictionary(new { 5 controller ="MemberCenter", 6 action="Detail" 7 }) 8 ).VirtualPath 9 10 %>
在第2個參數中將"controller"替換成"MemberCenter",將"action"替換成"Detail",所以在獲得網址之前,會先合并出一組新的路由值,如表:
欄位 |
值 |
controller |
MemberCenter |
action |
Detail |
id |
UrlParameter.Optional |
MVC會用這組新的路由值由上而下一一比對路由表中的所有路由規則,以得到最適合的路由規則。當此對比到第一個規則時,由於已經定義了兩個路由參數,分別是"{action}"和"{page}",而我們的路由表中只有"action"沒有"page",此時就會查看參數預設值中是否有預設的{page}參數,結果還是沒有,因此比對失敗。MVC並不會以這個網址路由來產生網址,進而跳至下一條網址路由進行比對。最後,比對的結果為"/MemberCenter/Detail"。
修改"/Views/Home/About.aspx‘頁面的程式碼,增加"page"參數,樣本如下:
1 <%= 2 RouteTable.Routes.GetVirtualPath( 3 Request.RequestContext, 4 new RouteValueDictionary(new { 5 controller ="MemberCenter", 6 action = "Detail", 7 page = "TEST" 8 }) 9 ).VirtualPath10 11 %>
執行以上操作後就會合并出一組新的路由值(RouteValue),如表:
欄位 |
值 |
controller |
MemberCenter |
action |
Detail |
id |
UrlParameter.Optional |
page |
TEST |
當我們比對第一條規則時,由於已經定義了兩個路由參數,分別是{action}和{page},而我們的路由表中已有"action"和"page"的路由值了,必要參數已經全部符合,所以會進一步比對限制條件是否符合。
由於已經定義了{page}參數的限制條件為“@"\d+"”,但路由值中的page值卻是"TEST",因限制條件無法通過,所以比對還是失敗了。因此,MVC並不會用這個網址路由來產生網址,進而跳至下一個網址路由進行比對。
最後比對結果為"/MemberCenter/Detail?page=TEST"。
總結:使用RouteTable.Routes.GetVirtualPath命名空間來產生網址大致會用到以下判斷依據。
- 若將第1個參數帶入Request.RequestContext命名空間,會預先取得當前所有的路由值。也可以輸入"null"代表沒有預設的路由值。
- 用當前合并後的所有路由值與網址路由表一一比對所有規則時,會先比對所有必要參數。若比對成功,就會進一步檢查限制條件是否符合。
- 若找不到必要參數,就會尋找參數預設值;如果仍然找不到,則比對失敗。
- 若上述比對全部成功,RouteTable.Routes.GetVirtualPath命名空間就會用該網址路由定義的網址來產生網址。
RouteTable.Routes.GetVirtualPath命名空間產生網址的完整流程圖。
四、MVC執行的生命週期
MVC的執行生命週期大致上分成3階段,分別是:
(1)網址路由比對;
(2)執行Controller與Action;
(3)執行View並返回結果。
4.1 網址路由比對
當IIS收到HTTP請求後,會先通過UrlRoutingModule處理所有與網址路由有關的運算。預設下若網址可以對應到網址根目錄下的實體檔案,就不會通過MVC進行處理,而是直接交由IIS或ASP.NET執行。
若網址是"http://localhostContent/Site.css",由於在網站根目錄下有"Content"目錄,而且"Content"目錄中也有Site.css檔案,所有MVC不會將此網址解析成Content控制器和Site.css動作。
再舉一個.NET Web Forms的例子,網址為"http://localhost/Member/Login.aspx",在Global.asax檔案中建立一個特殊的網址路由,具體如下。
1 routes.MapRoute( 2 "Default_aspx", // 路由名稱 3 "{controller}/{action.aspx}/{id}", // 帶有參數的 URL 4 new 5 { // 參數預設值 6 controller = "Home", 7 action = "Index", 8 id = UrlParameter.Optional 9 }10 );
在這種情況下,若在網站根目錄中有使用.NET Web Forms編寫的"/Member/Login.aspx"程式存在,MVC就不會應用UrlRouting,而是將流程的控制權交給IIS,並由IIS將其交友下一個模組執行。在此,就會執行"/Member/Login.aspx"程式。
如果"/Member/Login.aspx"程式不存在,那麼MVC的Routing就會正式啟動比對,並且在比對到上述網址後,將執行MemberController的Login動作。
若果在Global.asax檔案的Application_Start()事件的最前面將RouteTableRoutes.RouteExistingFiles參數值設定為"true",MVC的UrlRouting就不會先判斷是否有實體檔案存在,程式碼如下:
1 protected void Application_Start()2 {3 RouteTable.Routes.RouteExistingFiles = true;4 AreaRegistration.RegisterAllAreas();5 6 RegisterRoutes(RouteTable.Routes);7 }
設定完後,該網站收到的所以HTTP請求都會使用RegisterRoutes()方法中定義的網址路由規則一一進行比對。若比對成功,會用MVC進行處理;否則,將執行的權利交還給IIS。
NOTE : 若在MVC中是由IgnoreRoute()輔助方法比對成功的,會導致程式直接跳離MVC執行生命週期,將程式繼續執行的權利交還給IIS,由IIS覺得接下來該由哪個模組(Model)或哪個處理曆程(Handler)來執行。
在使用RegisterRoutes()方法定義的網址路由規則進行比對時,事實上,比對成功後,預設值會由MvcRouteHandler決定要將改HTTP請求發送給哪個HttpHandler來執行,。MVC預設會將HTTP請求交給MvcHandler來執行。
由圖所示的MvcRouteHandler程式碼可知,若要自訂RouteHandler,只要自行開發IRouteHandler介面的類,即可通過自訂的RouteHandler來決定通過網址路由比對的網址應該交給哪個HttpHandker來執行。所以,可以通過自訂RouteHandler和HttpHandler來為MVC網站提供協助工具功能。
4.2 執行Controller和Action 4.3 執行View並返回結果
當程式執行到MvcHandler時,HttpHandler的入口是ProcessRequest()方法。
MVC筆記 網址路由與MVC的生命週期