MVC5路由系統機制詳細講解


轉自:http://www.lanhusoft.com/Article/213.html
請求一個ASP.NET  mvc的網站和以前的web form是有區別的,ASP.NET MVC框架內部給我們提供了路由機制,當IIS接受到一個請求時,會先看是否請求了一個靜態資源(.html,css,js,圖片等),這一步是web form和mvc都是一樣的,如果不是說明則說明是請求的一個動態頁面,就會走asp.net的管道,mvc的程序請求都會走路由系統,會映射到一個Controller對應的Action方法,而web form請求動態頁面是會查找本地實際存在一個aspx文件。下面通過一個ASP.NET MVC5項目來詳細介紹一下APS.NET MVC5路由系統的機制。

一、認識Global.asax.cs

當我們創建一個APS.NET MVC5的項目的時候會在項目的根目錄中生成一個Global.asax文件。
  1. public class MvcApplication : System.Web.HttpApplication
  2. {
  3. protected void Application_Start()
  4. {
  5. //注冊 ASP.NET MVC 應用程序中的所有區域
  6. AreaRegistration.RegisterAllAreas();
  7. //注冊 全局的Filters
  8. FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  9. //注冊 路由規則
  10. RouteConfig.RegisterRoutes(RouteTable.Routes);
  11. //注冊 打包綁定(js,css等)
  12. BundleConfig.RegisterBundles(BundleTable.Bundles);
  13. }
  14. }

 

這個Application_Start方法會在網站啟動的自動調用,其中我們看到:RouteConfig.RegisterRoutes(RouteTable.Routes);這個就是向ASP.NET MVC 框架注冊我們自定義的路由規則,讓之后的URL能夠對應到具體的Action。接下來我們再來看看RegisterRoutes方法做了些什么?
  1. public class RouteConfig
  2. {
  3. public static void RegisterRoutes(RouteCollection routes)
  4. {
  5. routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  6. routes.MapRoute(
  7. name: "Default",
  8. url: "{controller}/{action}/{id}",
  9. defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  10. );
  11. }
  12. }

 

上面代碼是vs自動為你們生成,只定義了一個默認規則。
1、規則名:Default
2、URL分段:{controller}/{action}/{id},分別有三段,第一段對應controller參數,第段為action參數,第三段為id參數
3、URL段的默認值:controller為Home,action為Index,id = UrlParameter.Optional表示該參數為可選的。
之所以我們訪問http://www.xx.com/ 這樣的URL網址能正確返回,是因為我們設置了URL段的默認值,相當於訪問:
http://www.xx.com/Home/Index
 
RegisterRoutes調用的是RouteCollection的MapRoute方法,RouteCollection是一個集合,繼承於Collection<RouteBase>
 

三、ASP.NET MVC默認的命名約定

1、Controller命名約定

Controller類必須以Controller結尾,比如:HomeController,ProductController。我們在頁面上用HTML heper來引用一個Controller的時只需要前面Home,Product就可以,ASP.NET MVC框架自帶的DefaultControllerFactory自動為我們在結尾加上Controller,並開始根據這個名字開始找對應的類。我們創建一個ASP.NET MVC項目新加的Controller,文件會自動放在根目錄的Controllers文件夾里面,我們剛開始可以看到有一個HomeController.cs。當然你也可以實現接口IControllerFactory,定義自己的ControllerFactory來改變查找Controller文件的行為。我會再以后的文章中介紹。

2、View命名約定

ASP.NET MVC的視圖View默認情況是放在根目錄的Views文件下的,規則是這樣的:/Views/ControllerName/ActionName.cshtml。比如:HomeController的Action名字為Index的視圖對應文件為:/Views/Home/Index.cshtml
因此是通過Controller和Action的名字來確定視圖文件的位置的。采用這個命名約定的好處是在Action返回視圖的時候會MVC框架會按照這個約定找到默認的視圖文件。比如在ProductController的Action方法List最后是這樣的代碼:
return View();
會自動去路徑,/Views/Product/找文件List.cshtml(或者List.aspx如果使用的老的視圖引擎)。
當然也可以指定視圖的名字:
return View("~/Views/Product/List.cshtml")
或者
return View("MyOtherView")
 
MVC框架在查找具體的默認視圖文件時,如果在/Views/ControllerName/下面沒有找到,會再在/Views/Shared下面找,如果都沒找到就會找錯:找不到視圖。
 

四、ASP.NET MVC的URL規則說明

最開始我們在網站的Application_Start事件中注冊一些路由規則routes.MapRoute,當有請求過來的時候,mvc框架會用這些路由規則去匹配,一旦找到了符合要求就去處理這個URL。例如有下面這個URL:
http://mysite.com/Admin/Index
URL可以分為幾段,除去主機頭和url查詢參數,MVC框架是通過/來把URL分隔成幾段的。上面的URl分為兩段。如下圖:
第一段的值為Admin,第二段的值為Index,我們是很容易看出Admin對應就是Controller,Index就是Action。但是我們要告訴MVC框架這樣的規則,因此為下面的Application_Start有下面的代碼:
  1. routes.MapRoute(
  2. name: "Default",
  3. url: "{controller}/{action}/{id}",
  4. defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  5. );
上面表示URL規則是:
{controller}/{action} 
這個路由規則有兩個段,第一個是controller,第二個是action。聲明url段每個部分要且{}括起來,相當於占位符,是變量。
當一個URL請求到來的時候MVC路由系統就負責把它匹配到一個具體的路由規則,並把URL每段的值提取出來。這里說“一個具體的路由規則”,是因為可能會注冊多個路由規則,MVC路由系統會根據注冊順序一個一個的查找匹配,直到到為止。
默認情況,URL路由規則只匹配與之有相同URL段數量的URL。如下表:
URL
URL段
http://mysite.com/Admin/Index
controller = Admin
action = Index 
http://mysite.com/Index/Admin
controller = Index
action = Admin 
http://mysite.com/Apples/Oranges
controller = Apples
action = Oranges
http://mysite.com/Admin
無匹配-段的數量不夠
http://mysite.com/Admin/Index/Soccer
無匹配-段的數量超了
 

五、mvc創建一個簡單的Route規則

我們在前面注冊路由規則都是通過下面的方式:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}");
  3. }

 

用到了RouteCollection的MapRoute方法。其實我們還可以調用 Add方法,傳一個Route的實例給它一樣的達到相同的效果。
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. Route myRoute = new Route("{controller}/{action}", new MvcRouteHandler());
  3. routes.Add("MyRoute", myRoute);
  4. }

六、mvc路由的默認值的設定

之前有說:URL路由規則只匹配與之有相同URL段數量的URL,這種是嚴格,但是我們又想有些段不用輸入,讓用戶進入指定的頁面。像,http://www.xx.com/Home/就是進入進入Home的Index。只需要設定mvc路由的默認值就可以了。
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}", new { action = "Index" });
  3. }

 

要設置Controller和Action的默認值。
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}",
  3. new { controller = "Home", action = "Index" });
  4. }

 

下面是一個具體的Url對應的Route映射。
Url段的數量
實例
Route映射
0
mydomain.com 
controller = Home
action = Index 
1
mydomain.com/Customer
controller = Customer
action = Index 
2
mydomain.com/Customer/List
controller = Customer
action = List 
3
mydomain.com/Customer/List/All
無匹配—Url段過多 
 

七、mvc使用靜態URL段

前面定義路由規則都是占位符的形式,{controller}/{action},我們也可以使用在使用靜態字符串。如:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}",
  3. new { controller = "Home", action = "Index" });
  4. routes.MapRoute("", "Public/{controller}/{action}",
  5. new { controller = "Home", action = "Index" });
  6. }

 

上面匹配:http://mydomain.com/Public/Home/Index
路由:"Public/{controller}/{action}"只匹配有三段的url,第一段必須為Public,第二和第三可以是任何值,分別用於controller和action。
除此這外,路由規則中可以既包含靜態和變量的混合URL段,如:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("", "X{controller}/{action}");
  3. routes.MapRoute("MyRoute", "{controller}/{action}",
  4. new { controller = "Home", action = "Index" });
  5. routes.MapRoute("", "Public/{controller}/{action}",
  6. new { controller = "Home", action = "Index" });
  7. }

八、mvc的路由中自定義參數變量

mvc框架除了可以定義自帶的controller和action的參數之外,還可以定義自帶的變量。如下:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
  3. new { controller = "Home", action = "Index", id = "1" });
  4. }

 

上面定義了一個id,默認值我們設為1。
這個路由可以匹配0-3個url段的url,第三個url段將被用於id。如果沒有對應的url段,將應用設置的的默認值。
自定義參數變量使用:
方法一、
  1. public ViewResult CustomVariable() {
  2. ViewBag.CustomVariable = RouteData.Values["id"];
  3. return View();
  4. }

 

MVC框架從URL獲取到變量的值都可以通過RouteData.Values["xx"],這個集合訪問。
方法二、
public ViewResult CustomVariable(int id) {
 
ViewBag.CustomVariable = id;
    return View();
}
MVC框架使用內置的Model綁定系統將從URL獲取到變量的值轉換成Action參數相應類型的值。這種轉換除了可以轉換成基本int,string等等之外還可以處理復雜類型,自定義的Model,List集合等。相關參考: MVC中默認Model Binder綁定Action參數為List、Dictionary等集合的實例
 

九、mvc定義可選URL段、可選參數

asp.net mvc定義 參數是也可以設置為可選的,這樣用戶可以不用輸入這部分的參數。

1、注冊路由時定義可選URL段

public static void RegisterRoutes(RouteCollection routes) {
 
    routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
 

2、通過Action參數來定義可選參數

public ViewResult CustomVariable(string id = "DefaultId") {
 
ViewBag.CustomVariable = id;
    return View();
通過Action參數來定義可選參數是沒有加默認值的,而通過注冊路由時定義可選URL段是加了默認值的,是利用c#參數的默認參數特性。這樣如果用戶沒有輸入這部分url段,就會默認值就會被使用。
 

十、mvc使用*來定義變長數量的URL段

除了在路由規則中聲明固定的數量的URL段,我們也可以定義變長數量的URL段,如下面代碼:
public static void RegisterRoutes(RouteCollection routes) {
 
    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
通過變量前面加一個星號(*)開頭就能匹配任意變長數量的URL。
匹配URL如下:
Url段的數量
實例
Route映射
0
mydomain.com 
controller = Home
action = Index 
1
mydomain.com/Customer
controller = Customer
action = Index 
2
mydomain.com/Customer/List
controller = Customer
action = List 
3
mydomain.com/Customer/List/All
controller = Customer
action = List
id = All 
4
mydomain.com/Customer/List/All/Delete
controller = Customer
action = List
id = All
catchall = Delete  
5
mydomain.com/Customer/List/All/Delete/Perm
controller = Customer
action = List
id = All
catchall = Delete /Perm

十一、mvc使用命名空間來為路由的Controller類定優先級

當一個用戶輸入一個URL請求ASP.NET MVC的網站時,ASP.NET MVC會根據URL的獲取請求是找到是哪一個Controller類,如果一個項目有多相同的類名的Controller,就會有問題。比如:當請求的變量controller的值為Home時,MVC框架就會去找一個Controller名字為HomeController的類,這個類(HomeController)默認是不受限制的,如果多個命名空間都有名字為HomeContoller的類, ASP.NET MVC就不知道怎么辦了。當這種情況發生是,就會報錯:
“/”應用程序中的服務器錯誤。
找到多個與名為“Home”的控制器匹配的類型。如果為此請求(“{controller}/{action}/{id}”)提供服務的路由沒有指定命名空間以搜索與此請求相匹配的控制器,則會發生這種情況。如果是這樣,請通過調用帶有 'namespaces' 參數的 "MapRoute" 方法的重載來注冊此路由。
 
“Home”請求找到下列匹配的控制器:
WebApplication1.Controllers.HomeController
WebApplication1.Controllers1.HomeController 
 
[InvalidOperationException: 找到多個與名為“Home”的控制器匹配的類型。如果為此請求(“{controller}/{action}/{id}”)提供服務的路由沒有指定命名空間以搜索與此請求相匹配的控制器,則會發生這種情況。如果是這樣,請通過調用帶有 'namespaces' 參數的 "MapRoute" 方法的重載來注冊此路由。
“Home”請求找到下列匹配的控制器:
解決辦法:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("Default",
  3. "{controller}/{action}/{id}",
  4. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  5. new string[] { "WebApplication1.Controllers" }
  6. );
  7. }

 

上面MapRoute的最后一個參數,new string[] { "WebApplication1.Controllers" }就是指定先去命名空間為WebApplication1.Controllers查找在controller,如果找到就停止往下找,沒找到還是會去其它命名空間中去找的。因此當你指定的這個命名空間如果沒存在要找的controller類,而在其它命名空間是有的,是會正常執行的,所以這里指定命名空間並不是限定了命名空間,而只是設了一個優先級而已。
 

十二、mvc定義路由規則的約束

在前面我們介紹了為mvc路由的規則設置路由默認值和可選參數,現在我們再深入一點,我們要約束一下路由規則。

1、用正則表達式限制asp.net mvc路由規則

  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
  3. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  4. new { controller = "^H.*"},
  5. new[] { "URLsAndRoutes.Controllers"});
  6. }

 

上面用到正則表達式來限制asp.net mvc路由規則,表示只匹配contorller名字以H開頭的URL。

2、把asp.net mvc路由規則限制到到具體的值

  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
  3. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  4. new { controller = "^H.*", action = "^Index$|^About$"},
  5. new[] { "URLsAndRoutes.Controllers"});
  6. }

 

上例在controller和action上都定義了約束,約束是同時起作用是,也就是要同時滿足。上面表示只匹配contorller名字以H開頭的URL,且action變量的值為Index或者為About的URL。
 

3、把asp.net mvc路由規則限制到到提交請求方式(POST、GET)

  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
  3. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  4. new { controller = "^H.*", action = "Index|About",
  5. httpMethod = new HttpMethodConstraint("GET") },
  6. new[] { "URLsAndRoutes.Controllers" });
  7. }

上面表示只匹配為GET方式的請求。

4、使用接口IRouteConstraint自定義一個asp.net mvc路由約束

下面我自定義一個約束對特定瀏覽器進行處理。
UserAgentConstraint.cs:
  1. using System.Web;
  2. using System.Web.Routing;
  3. namespace URLsAndRoutes.Infrastructure {
  4. public class UserAgentConstraint : IRouteConstraint {
  5. private string requiredUserAgent;
  6. public UserAgentConstraint(string agentParam) {
  7. requiredUserAgent = agentParam;
  8. }
  9. public bool Match(HttpContextBase httpContext, Route route, string parameterName,
  10. RouteValueDictionary values, RouteDirection routeDirection) {
  11. return httpContext.Request.UserAgent != null &&
  12. httpContext.Request.UserAgent.Contains(requiredUserAgent);
  13. }
  14. }
  15. }

 

asp.net mvc自定義路由約束的使用:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
  3. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  4. new {
  5. controller = "^H.*", action = "Index|About",
  6. httpMethod = new HttpMethodConstraint("GET", "POST"),
  7. customConstraint = new UserAgentConstraint("IE")
  8. },
  9. new[] { "URLsAndRoutes.Controllers" });
  10. }
上面表示這個路由規則只匹配用戶使用IE瀏覽器的請求。利用這點我們就可以實現不同瀏覽器使用不同的Controller,進行不同的處理。雖然這樣做的意義不大,但是不排除有時會有這種變態的需求。
 

十三、mvc將URL路由到磁盤文件

mvc的網站並不是所以的url請求都是對應controller,action,我們仍然要一種方式來提供一些靜態內容,比如:html文件,css,圖片,javascript文件些,其實默認情況下mvc框架在在收到url請求時會先判斷這個url是否是對應一個磁盤中真實存在的文件,如果是直接返回,這時路由是沒有使用到的,如果不是真實存在的文件時才會走路由系統,再去匹配注冊的路由規則。
這種默認的處理url機制順序我們也可以改變它,讓在檢查物理文件之前就應用路由,如下:
  1. public static void RegisterRoutes(RouteCollection routes) {
  2. routes.RouteExistingFiles = true;
  3. routes.MapRoute("DiskFile", "Content/StaticContent.html",
  4. new {
  5. controller = "Account", action = "LogOn",
  6. },
  7. new {
  8. customConstraint = new UserAgentConstraint("IE")
  9. });
  10. routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
  11. new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  12. new {
  13. controller = "^H.*", action = "Index|About",
  14. httpMethod = new HttpMethodConstraint("GET", "POST"),
  15. customConstraint = new UserAgentConstraint("IE")
  16. },
  17. new[] { "URLsAndRoutes.Controllers" });
  18. }

 

 
我們把RouteExistingFiles屬性設置為true,表示存在的文件也走路由,上面我們把Content/StaticContent.html這個文件映射到controller 為Account,action 為LogOn中了,而並不是指磁盤中存在的文件。基於asp.net mvc的這個特性我們就可以實現mvc以.html結尾的偽靜態,具體實現方式請看我以前寫的文章: 教你如何在asp.net mvc中實現高性能以html結尾的偽靜態
 

十四、mvc跳過、繞開路由系統設定

上面我們用使用routes.RouteExistingFiles = true,讓所有的請求都走路由系統過一下,難免有一些性能影響,因為一些圖片,一些真正的html,文件是沒有必要的。我們可以對些文件做一些起特殊設定讓它們跳過、繞開路由系統。下面就是讓Content目錄下的所有文件都繞開mvc的路由系統:
    1. public static void RegisterRoutes(RouteCollection routes) {
    2. routes.RouteExistingFiles = true;
    3. routes.MapRoute("DiskFile", "Content1/StaticContent.html",
    4. new {
    5. controller = "Account", action = "LogOn",
    6. },
    7. new {
    8. customConstraint = new UserAgentConstraint("IE")
    9. });
    10. routes.IgnoreRoute("Content/*{filename}");
    11. routes.MapRoute("", "{controller}/{action}");
    12. }


免責聲明!

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



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