.NetCore MVC中的路由(1)路由配置基礎

durow發表於2016-10-24

.NetCore MVC中的路由(1)路由配置基礎

 

0x00 路由在MVC中起到的作用

前段時間一直忙於別的事情,終於搞定了繼續學習.NetCore。這次學習的主題是MVC中的路由。路由是所有MVC框架都會實現的一個元件,核心功能就是根據接收到的Http請求中的Path(對於http://localhost/Home/Index/12?test=555 來說,http是協議,localhost是域,Home/Index/12是Path,test=555是引數)部分,依次和路由規則集合中的規則進行匹配,匹配成功後由對應的Controller中的對應Action進行Http請求的處理。匹配不到則返回404錯誤。

 

大多數MVC框架路由規則的配置都大同小異,一般都是通過模板的方式來配置路由規則。有的還支援在Controller和Action上通過Attribute(Java中叫註解)進行更細粒度的配置。

.NetCore MVC支援通過全域性的路由模板配置路由規則,也支援在Controller和Action上通過Attribute進行細粒度的路由配置。下面先說一下在Startup.cs中配置全域性路由規則。

0x01 在Startup.cs中配置路由

所謂的路由的模板就是一串字串,當接收到Http請求後取出其中的Path部分,和模板進行對照,如果匹配模板則路由到對應的Controller和Action進行處理。我們可以在Startup.cs檔案中的Configure方法中,新增MVC功能時進行路由配置,例如:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action}/{id}");
});

其中name為路由規則的名稱,template為路由模板。這也引出了我們第一個概念,路由模板中的變數。

1.路由模板中的變數

在模板"{controller }/{action }/{id}"中,用花括號括起來的是路由模板中的變數。例如其中變數的作用並不是必須在Path中匹配某個固定的字串,而是起到一個佔位的作用,例如上面的模板就可以匹配由“/”隔開的共三部分的Path,例如a/b/c可以匹配成功。而各個變數的值從Path中對應部分提取出來。例如

Home/Index/12可以匹配,其中controller為Home,action為Index,id為12

Home/Index則匹配失敗,因為只有2部分

Home/Index/12/34同樣匹配失敗,因為超過了3部分。

模板匹配成功後,會根據controller和action提取出的值路由:

Home/Index/12會路由到HomeController的Index方法,變數id為12

Test/Show/ab會路由到TestController的Show方法,變數id為ab

2.變數值得獲取

在Index或Show方法中,我們可以有兩種方法提取變數:

一種是在方法的引數列表中加入和變數相同名稱的引數,MVC會自動從變數列表中尋找並轉換為對應型別:

public IActionResult Index(string id, string controller, string action)
{
    ViewData["Message"] = "id is " + id + ",  controller is " + controller + ",  action is " + action;
    return View();
}

另一種就是從RouteData中取出:

public IActionResult Index()
{
    var controller = RouteData.Values["controller"].ToString();
    var action = RouteData.Values["action"].ToString();
    var id = RouteData.Values["id"].ToString();
    ViewData["Message"] = "id is " + id + ",  controller is " + controller + ",  action is " + action;
    return View();
}

路由模板中的變數名稱是可以自己定義的,但controller和action(包括後面講的area)都是比較特殊的變數。其中controller提取出的值作為Controller的名稱,action提取出的值作為Controller中方法的名稱。為了讓每條路由規則都能夠路由到Controller和Action,在路由模板中都應該出現controller和action變數,但我們也可以給controller和action變數指定預設值,這樣在Path中省略了這部分時會用預設值代替。

3.變數的預設值

由兩種方法可以配置變數的預設值:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id=0}");
});

或者

routes.MapRoute(
    name: "default",
    template: "{controller}/{action}/{id}",
    defaults: new 
    {
        controller = ”Home”,
        action = ”Index”,
        id = 0,
    });

這樣配置後Path中帶有預設值的部分可以省略,省略的規則和C#中帶預設值的引數一樣,例如:

空Path會被路由到HomeController,Index方法

Test會被路由到TestController,Index方法

Test/Show依然會被路由到TestController,Show方法

一般我會用第一種方法配置預設值,更加直觀和方便。但有時候有些需求是第一種方法難以做到的。例如我想給TestController的Show方法配置路由為TestShow,使用第一種方法可以這樣配置:”TestShow/{controller=Test}/{action=Show}”,這樣配置當Path為TestShow時的確可以路由到TestController的Show方法,但當Path為TestShow/Home/Index時會路由到HomeController的Index方法。

使用第二種方法配置:

routes.MapRoute(
    name: "test",
    template: "TestShow",
    defaults: new 
    {
        controller = ”Test”,
        action = ”Show”, 
    });

當Path為TestShow時可以路由到TestController的Show方法,但Path為Test/Home/Index則無法匹配模板。關於細粒度的路由配置更好的方法是給Test方法使用Route特性(Attribute)進行配置,後面會說到。

4.路由規則中的靜態字元

除了使用變數來配置路由模板,還可以使用靜態字串。靜態字串可以單獨使用,也可以與變數混合使用。

例如模板為:

”Durow/{controller}/{action}”

Durow/Home/About會路由到HomeController,About方法

Durow/Test/Show會路由到TestController,Show方法

也可以把靜態字元和變數混合起來,例如配置模板為:

”My{controller}/My{action}”

MyHome/MyAbout會被路由到HomeController,About方法

MyTest/MyShow會被路由到TestController,Show方法

5.使用?標記變數可選

除了通過給變數提供預設值使其可選外,也可以使用?把變數標記為可選。例如模板

“{controller}/{action}/{id?}”

其中id為可選變數,這樣配置後

Home/Index和Home/Index/12都會成功匹配。

6.使用*提取Path中剩餘的所有部分

如果一個模板需要匹配包含任意多個部分(Segments)的Path,可以使用*符號指定變數,使用*制定過的變數會把Path中匹配完成後剩餘部分全部提取出來,例如模板:

”{controller}/{action}/{id?}/{*others}”

Home/Index/12/a/b/c/d,會路由到HomeController的Index方法,id為12,others為a/b/c/d

實際上僅從模板匹配的角度來說,上面的模板可以匹配所有的Path。唯一的問題就是匹配後對應的Controller和Action可能不存在。

7.多條路由規則的選擇

實際應用中很可能會配置多條路由規則,當接收到Path時很可能不止一條規則能夠匹配。

最簡單的,我們配置以下兩條模板:

“{controller }/{action =About}”

“{controller }/{action =Index }”

當Path為Home時兩條路由都能匹配,那要怎麼選擇呢?其實很簡單粗暴,就是看哪條路由在前面。也就是說Path一旦成功匹配到模板後就會立即實施路由並忽略後面的模板。對於上面的配置來說Home會被路由到HomeController的About方法。所以在配置路由時一定要注意順序。

0x02 使用Attribute配置路由

除了在Startup.cs中配置全域性路由規則外,也可以針對單個Controller和其中的Action配置路由。方法就是在Controller類和Action方法上使用Route特性。例如在TestController的Show方法上使用Route特性:

[Route("TestShow")]
public IActionResult Show()
{
    return View();
}

當Path為TestShow時,即可路由到TestController的Show方法。

上面我們在介紹預設值時提到過,通過全域性模板配置:

routes.MapRoute(
    name: "test",
    template: "TestShow",
    defaults: new 
    {
        controller = ”Test”,
        action = ”Show”, 
    });

也可以達到同樣的目的。不過區別在於,使用後一種方法時,如果還有”{controller}/{action}”這樣的模板,除了TestShow外,當Path為Test/Show可以匹配這個模板並路由到TestController的Show方法。而通過在Show方法上配置Route特性後,只有TestShow才可以路由,即使同時存在”{controller}/{action}”這樣的模板,Test/Show也無法路由。

第一次接觸用Route特性配置路由時,我很疑惑路由元件是如何把Path路由到對應的Controller和Action的,後來下了個斷點看了下RouteData物件,發現對於配置了路由的Action方法,其controller為方法所在的Controller的名稱,action為方法的名稱,而且在Route特性配置的路由模板中不能夠使用{controller}變數和{action}變數。這樣就保證了匹配模板的Path總能路由到這個Action。

對於在Controller類上配置的Route特性最終會分別配置到Controller中的每個Action上。例如我們在TestController上配置Route(“TestShow”),實際上就是給每個方法配置了Route(“TestShow"),所以當Path為TestShow時會報錯,提示有兩個action滿足匹配。那麼應當如何給Controller通過Route配置路由呢,可以使用[controller]和[action]。

Route特性中的[controller]和[action]

對於[controller]和[action]我也不知道該怎麼叫,不能叫變數,功能上類似佔位符。當我們在Controller類用Route特性配置路由時,如果使用了[controller]和[action],這樣當Route特性給Controller中每個Action配置路由時,[controller]會被替換為Controller名稱,[action]會被替換為Action名稱。舉個例子還是給TestController配置Route特性,配置為Route(“durow/[controller]/[action]”),這樣對於其中的Index方法來說,其路由模板為”durow/Test/Index”,controller為Test,action為Index。而對於Show方法來說路由模板為”durow/Test/Show”,controller為Test,action為Show。前面說過MVC會為每個Action建立一個ActionDescriptor物件儲存這個Action的路由資訊。對於配置了Route特性的Action(再重複一下,給Controller類配置Route特性相當於給Controller中的每個Action配置Route特性),其ActionDescriptor中會有一個AttributeRouteInfo物件,對於未配置Route特性的Action,該物件為空。AttributeRouteInfo中包含了路由模板資訊。

 

所以對於上面TestController的Route特性的配置,配置為Route(“durow/Test/[action]”)也能達到同樣的效果。不過使用Route(“durow/[controller]/[action]”)語義更強更通用。

在Route特性中使用變數

在Route特性中配置模板也是可以使用變數的,同樣可以使用?標記變數可選。例如可以給TestController配置Route(“durow/[controller]/[action]/{id?}”)。但需要注意的是Route特性的模板中變數不能使用預設值(包括[controller]和[action]),也不能使用*提取Path所有剩餘部分。

0x03 寫在最後

囉囉嗦嗦居然寫了這麼多,其實實際使用中很可能用不到多麼複雜的路由,一般一條通用規則,一條Area相關的規則就可以了。不過詳細瞭解了路由規則,當以後遇到有些奇葩的特殊需求時能夠有更加開闊的思路。後面講討論一下路由模板中的約束和自定義約束。再後面討論一下使用Areas。

 


更多內容歡迎訪問我的部落格:http://www.durow.vip

 

相關文章