ASP.NET MVC5路由系統機制詳細講解
ASP.NET MVC5路由系統機制詳細講解
https://blog.csdn.net/slowlifes/article/details/72461440
請求一個ASP.NETmvc的網站和以前的web form是有區別的,ASP.NETMVC框架內部給我們提供了路由機制,當IIS接受到一個請求時,會先看是否請求了一個靜態資源(.html,css,js,圖片等),這一步是web form和mvc都是一樣的,如果不是則說明是請求的是一個動態頁面,就會走asp.net的管道,mvc的程式請求都會走路由系統,會對映到一個Controller對應的Action方法,而web form請求動態頁面是會查詢本地實際存在一個aspx檔案。下面通過一個ASP.NETMVC5專案來詳細介紹一下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 }
二、ASP.NETMVC預設的命名約定
1、Controller命名約定
Controller類必須以Controller結尾,比如:HomeController,ProductController。我們在頁面上用HTML heper來引用一個Controller的時只需要前面Home,Product就可以,ASP.NETMVC框架自帶的DefaultControllerFactory自動為我們在結尾加上Controller,並開始根據這個名字開始找對應的類。我們建立一個ASP.NETMVC專案新加的Controller,檔案會自動放在根目錄的Controllers資料夾裡面,我們剛開始可以看到有一個HomeController.cs。當然你也可以實現介面IControllerFactory,定義自己的ControllerFactory來改變查詢Controller檔案的行為。我會再以後的文章中介紹。2、View命名約定
ASP.NETMVC的檢視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 3 routes.MapRoute("MyRoute", "{controller}/{action}"); 4 }
用到了RouteCollection的MapRoute方法。其實我們還可以呼叫 Add方法,傳一個Route的例項給它一樣的達到相同的效果。
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 Route myRoute = new Route("{controller}/{action}", new MvcRouteHandler()); 4 routes.Add("MyRoute", myRoute); 5 }
五、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 3 routes.MapRoute("MyRoute", "{controller}/{action}", 4 new { controller = "Home", action = "Index" }); 5 }下面是一個具體的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 3 routes.MapRoute("MyRoute", "{controller}/{action}", 4 new { controller = "Home", action = "Index" }); 5 6 routes.MapRoute("", "Public/{controller}/{action}", 7 new { controller = "Home", action = "Index" }); 8 }上面匹配:http://mydomain.com/Public/Home/Index 路由:"Public/{controller}/{action}"只匹配有三段的url,第一段必須為Public,第二和第三可以是任何值,分別用於controller和action。 除此這外,路由規則中可以既包含靜態和變數的混合URL段,如:
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("", "X{controller}/{action}"); 4 5 routes.MapRoute("MyRoute", "{controller}/{action}", 6 new { controller = "Home", action = "Index" }); 7 8 routes.MapRoute("", "Public/{controller}/{action}", 9 new { controller = "Home", action = "Index" }); 10 11 }
七、mvc的路由中自定義引數變數
mvc框架除了可以定義自帶的controller和action的引數之外,還可以定義自帶的變數。如下:1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("MyRoute", "{controller}/{action}/{id}", 4 new { controller = "Home", action = "Index", id = "1" }); 5 }上面定義了一個id,預設值我們設為1。 這個路由可以匹配0-3個url段的url,第三個url段將被用於id。如果沒有對應的url段,將應用設定的的預設值。 自定義引數變數使用: 方法一、
1 public ViewResult CustomVariable() { 2 3 ViewBag.CustomVariable = RouteData.Values["id"]; 4 return View(); 5 }MVC框架從URL獲取到變數的值都可以通過RouteData.Values["xx"],這個集合訪問。 方法二、
1 public ViewResult CustomVariable(int id) { 2 3 ViewBag.CustomVariable = id; 4 return View(); 5 }
MVC框架使用內建的Model繫結系統將從URL獲取到變數的值轉換成Action引數相應型別的值。這種轉換除了可以轉換成基本int,string等等之外還可以處理複雜型別,自定義的Model,List集合等。
八、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 3 routes.MapRoute("Default", 4 "{controller}/{action}/{id}", 5 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 6 new string[] { "WebApplication1.Controllers" } 7 ); 8 }上面MapRoute的最後一個引數,new string[] { "WebApplication1.Controllers" }就是指定先去名稱空間為WebApplication1.Controllers查詢在controller,如果找到就停止往下找,沒找到還是會去其它名稱空間中去找的。因此當你指定的這個名稱空間如果沒存在要找的controller類,而在其它名稱空間是有的,是會正常執行的,所以這裡指定名稱空間並不是限定了名稱空間,而只是設了一個優先順序而已。
十一、mvc定義路由規則的約束
在前面我們介紹了為mvc路由的規則設定路由預設值和可選引數,現在我們再深入一點,我們要約束一下路由規則。1、用正則表示式限制asp.net mvc路由規則
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 4 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 5 new { controller = "^H.*"}, 6 new[] { "URLsAndRoutes.Controllers"}); 7 }上面用到正則表示式來限制asp.net mvc路由規則,表示只匹配contorller名字以H開頭的URL。
2、把asp.net mvc路由規則限制到到具體的值
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 4 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 5 new { controller = "^H.*", action = "^Index$|^About$"}, 6 new[] { "URLsAndRoutes.Controllers"}); 7 }
上例在controller和action上都定義了約束,約束是同時起作用是,也就是要同時滿足。上面表示只匹配contorller名字以H開頭的URL,且action變數的值為Index或者為About的URL。
3、把asp.net mvc路由規則限制到到提交請求方式(POST、GET)
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 4 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 5 new { controller = "^H.*", action = "Index|About", 6 httpMethod = new HttpMethodConstraint("GET") }, 7 new[] { "URLsAndRoutes.Controllers" }); 8 }
上面表示只匹配為GET方式的請求。
4、使用介面IRouteConstraint自定義一個asp.net mvc路由約束
下面我自定義一個約束對特定瀏覽器進行處理。 UserAgentConstraint.cs:1 using System.Web; 2 using System.Web.Routing; 3 4 namespace URLsAndRoutes.Infrastructure { 5 6 public class UserAgentConstraint : IRouteConstraint { 7 private string requiredUserAgent; 8 9 public UserAgentConstraint(string agentParam) { 10 requiredUserAgent = agentParam; 11 } 12 13 public bool Match(HttpContextBase httpContext, Route route, string parameterName, 14 RouteValueDictionary values, RouteDirection routeDirection) { 15 16 return httpContext.Request.UserAgent != null && 17 httpContext.Request.UserAgent.Contains(requiredUserAgent); 18 } 19 } 20 }
asp.net mvc自定義路由約束的使用:
1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 4 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 5 new { 6 controller = "^H.*", action = "Index|About", 7 httpMethod = new HttpMethodConstraint("GET", "POST"), 8 customConstraint = new UserAgentConstraint("IE") 9 }, 10 new[] { "URLsAndRoutes.Controllers" }); 11 }上面表示這個路由規則只匹配使用者使用IE瀏覽器的請求。利用這點我們就可以實現不同瀏覽器使用不同的Controller,進行不同的處理。雖然這樣做的意義不大,但是不排除有時會有這種變態的需求。
十二、mvc將URL路由到磁碟檔案
mvc的網站並不是所以的url請求都是對應controller,action,我們仍然要一種方式來提供一些靜態內容,比如:html檔案,css,圖片,javascript檔案些,其實預設情況下mvc框架在在收到url請求時會先判斷這個url是否是對應一個磁碟中真實存在的檔案,如果是直接返回,這時路由是沒有使用到的,如果不是真實存在的檔案時才會走路由系統,再去匹配註冊的路由規則。 這種預設的處理url機制順序我們也可以改變它,讓在檢查物理檔案之前就應用路由,如下:1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.RouteExistingFiles = true; 4 5 routes.MapRoute("DiskFile", "Content/StaticContent.html", 6 new { 7 controller = "Account", action = "LogOn", 8 }, 9 new { 10 customConstraint = new UserAgentConstraint("IE") 11 }); 12 13 routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 14 new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 15 new { 16 controller = "^H.*", action = "Index|About", 17 httpMethod = new HttpMethodConstraint("GET", "POST"), 18 customConstraint = new UserAgentConstraint("IE") 19 }, 20 new[] { "URLsAndRoutes.Controllers" }); 21 }
我們把RouteExistingFiles屬性設定為true,表示存在的檔案也走路由,上面我們把Content/StaticContent.html這個檔案對映到controller為Account,action為LogOn中了,而並不是指磁碟中存在的檔案。基於asp.net mvc的這個特性我們就可以實現mvc以.html結尾的偽靜態
十三、mvc跳過、繞開路由系統設定
上面我們用使用routes.RouteExistingFiles = true,讓所有的請求都走路由系統過一下,難免有一些效能影響,因為一些圖片,一些真正的html,檔案是沒有必要的。我們可以對些檔案做一些起特殊設定讓它們跳過、繞開路由系統。下面就是讓Content目錄下的所有檔案都繞開mvc的路由系統:1 public static void RegisterRoutes(RouteCollection routes) { 2 3 routes.RouteExistingFiles = true; 4 5 routes.MapRoute("DiskFile", "Content1/StaticContent.html", 6 new { 7 controller = "Account", action = "LogOn", 8 }, 9 new { 10 customConstraint = new UserAgentConstraint("IE") 11 }); 12 13 routes.IgnoreRoute("Content/*{filename}"); 14 routes.MapRoute("", "{controller}/{action}"); 15 16 }