1. 程式人生 > >MVC5中路由新特性

MVC5中路由新特性



1、什麼是Attribute路由?怎麼樣啟用Attribute路由?

  微軟在 ASP.NET MVC5 中引入了一種新型路由:Attribute路由,顧名思義,Attribute路由是通過Attribute來定義路由。當然,MVC5也支援以前定義路由的方式,你可以在一個專案中混合使用這兩種方式來定義路由。

  在以前的版本中我們通常在 RouteConfig.cs 檔案中通過以下方式來定義路由:

routes.MapRoute(

   name: "ProductPage",

   url: "{productId}/{productTitle}",

   defaults: new { controller = "Products", action = "Show" },

   constraints: new { productId = "\\d+" }

);

  在MVC5中,我們可以把路由定義和 Action 放在一起:

[Route("{productId:int}/{productTitle}")]

public ActionResult Show(int productId) { ... }

  當然,首先得啟用Attribute路由,我們可以呼叫MapMvcAttributeRoutes方法來啟用Attribute路由:

public class RouteConfig

{

   public static void RegisterRoutes(RouteCollection routes)

   {

       routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

       routes.MapMvcAttributeRoutes();

   }

}

2、URL可選引數和預設值

  我們可以使用問號“?”來標記一個可選引數,也可以對引數設定預設值:

public class BooksController : Controller

{

   // 匹配: /books

   // 匹配: /books/1430210079

// 問號表示 isbn 是可選的

   [Route("books/{isbn?}")]

   public ActionResult View(string isbn)

   {

       if (!String.IsNullOrEmpty(isbn))

       {

           return View("OneBook", GetBook(isbn));

       }

       return View("AllBooks", GetBooks());

   }

   // 匹配: /books/lang

   // 匹配: /books/lang/en

   // 匹配: /books/lang/he

// 如果URL不傳遞 lang 引數,則lang的值為“en”

   [Route("books/lang/{lang=en}")]

   public ActionResult ViewByLanguage(string lang)

   {

       return View("OneBook", GetBooksByLanguage(lang));

   }

}

3、路由字首

  有時候在同一個 Controller 中,所有 Action 匹配的 URL 都擁有相同的字首,如下:

public class ReviewsController : Controller

{

   // 匹配: /reviews

   [Route("reviews")]

   public ActionResult Index() { ... }

   // 匹配: /reviews/5

   [Route("reviews/{reviewId}")]

   public ActionResult Show(int reviewId) { ... }

   // 匹配: /reviews/5/edit

   [Route("reviews/{reviewId}/edit")]

   public ActionResult Edit(int reviewId) { ... }

}

  我們看到 ReviewsController 下的所有 Action 前面都帶有 "reviews",這時我們可以在 Controller 上使用 [RoutePrefix]設定路由字首,為每個 Action 所匹配的 URL 加上共同的字首 "reviews":

[RoutePrefix("reviews")]

public class ReviewsController : Controller

{

   // 匹配: /reviews

   [Route]

   public ActionResult Index() { ... }

   // 匹配: /reviews/5

   [Route("{reviewId}")]

   public ActionResult Show(int reviewId) { ... }

   // 匹配: /reviews/5/edit

   [Route("{reviewId}/edit")]

   public ActionResult Edit(int reviewId) { ... }

}

  但是,如果某一個 Action 不想要這個字首怎麼辦?當然有辦法,我們可以用波浪號“~”來去掉它:

[RoutePrefix("reviews")]

public class ReviewsController : Controller

{

   // 匹配: /spotlight-review

   [Route("~/spotlight-review")]

   public ActionResult ShowSpotlight() { ... }

   ...

}

4、預設路由

  我們除了可以在 Action 上使用[Route]外,也可以用在 Controller 上,當[Route]用在 Controller 上時,它就定義了一個預設路由規則,它會對這個 Controller 下的所有 Action 起作用,除非某個 Action 上也應用了 [Route] 特性覆蓋了 Controller 上的[Route]。但要注意的是應用在 Controller 上的 [Route] 一定要加上 {action},否則會丟擲“RouteData 必須包含名為'action'且值為非空字串的項。”錯誤。應用在 Action 上的 [Route] 則不用加,因為 {action} 就是當前 Action。

[RoutePrefix("promotions")]

[Route("{action=index}")]

//上面定義了預設路由,並且{action}的預設值為"index",

//也就是說 URL 不包含 {action} 時,預設呼叫的 Action 是 Index。

public class ReviewsController : Controller

{

   // 匹配: /promotions

   public ActionResult Index() { ... }

   // 匹配: /promotions/archive

   public ActionResult Archive() { ... }

   // 匹配: /promotions/new

   public ActionResult New() { ... }

   // 匹配: /promotions/edit/5

// 這裡覆蓋了預設路由規則

// 按照預設路由,這裡應該匹配:/promotions/editProduct?promoId=5

   [Route("edit/{promoId:int}")]

   public ActionResult EditProduct(int promoId) { ... }

}

5、路由約束

  路由約束可以讓你指定引數的型別以及範圍等,格式為:{引數:約束},舉例如下:

// 匹配: /users/5

[Route("users/{id:int}"]

// 這裡約束了引數“id”必須為整數型別

public ActionResult GetUserById(int id) { ... }

  下面是支援的路由約束列表:

  • alpha,必須為大小寫字母(a-z,A-Z),如:{x:alpha};

  • bool,必須為布林值,如:{x:bool}

  • datetime,必須為DateTime(時間和日期)型別,如:{x:datetime}

  • decimal,必須為decimal型別,如:{x:decimal}

  • double,必須為64bit浮點數,如:{x:double}

  • float,必須為32bit浮點數,如:{x:float}

  • guid,必須為GUID,如:{x:guid}

  • int,必須為32bit整數,如:{x:int}

  • length,字串長度必須為指定值或者在指定範圍內,如:{x:length(6)} {x:length(1,20)}

  • long,必須為64bit整數,如:{x:long}

  • max,小於等於指定值的整數,如:{x:max(10)}

  • maxlength,字串長度小於等於指定值,如:{x:maxlength(10)}

  • min,大於等於指定值的整數整數,如:{x:min(10)}

  • minlength,字串長度大於等於指定值,如:{x:minlength(10)}

  • range,必須是給定範圍內的整數,如:{x:range(10,50)}

  • regex,必須與正則表示式匹配,如:{x:(^\d{3}-\d{3}-\d{4}$)}

  你可以在一個引數後面應用多個約束,用冒號分隔它們,如下:

// 匹配: /users/5

// 但是不匹配 /users/10000000000 因為id的值已經超過了int.MaxValue,

// 也不匹配 /users/0 因為後面有個min(1)約束,id 的值必須大於等於 1.

[Route("users/{id:int:min(1)}")]

public ActionResult GetUserById(int id) { ... }

  值得注意的是加在可選引數上的約束,例如:

// 匹配: /greetings/bye

// 也匹配 /greetings 因為message是可選引數,

// 但是不匹配 /greetings/see-you-tomorrow 因為有maxlength(3)約束.

[Route("greetings/{message:maxlength(3)?}")]

public ActionResult Greet(string message) { ... }

6、自定義路由約束

  我們可以通過實現 IRouteConstraint 介面來自定義路由約束。下面的例子展示如何自定義路由約束:

public class ValuesConstraint : IRouteConstraint

{

   private readonly string[] validOptions;

   public ValuesConstraint(string options)

   {

       validOptions = options.Split('|');

   }

   public bool Match(HttpContextBase httpContext, Route route,

       string parameterName, RouteValueDictionary values,

       RouteDirection routeDirection)

   {

       object value;

       if (values.TryGetValue(parameterName, out value) && value != null)

       {

           return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);

       }

       return false;

   }

}

下面的程式碼是顯示如何註冊自定義的路由約束

public class RouteConfig

{

   public static void RegisterRoutes(RouteCollection routes)

   {

       routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

       var constraintsResolver = new DefaultInlineConstraintResolver();

       constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));

       routes.MapMvcAttributeRoutes(constraintsResolver);

   }

}

現在我們就可以在程式碼中使用這些自定義的路由約束了:

public class TemperatureController : Controller

{

   // 匹配 temp/celsius 以及 /temp/fahrenheit 但不匹配 /temp/kelvin

   [Route("temp/{scale:values(celsius|fahrenheit)}")]

   public ActionResult Show(string scale)

   {

       return Content("scale is " + scale);

   }

}

7、路由名稱

  你可以為路由規則指定一個名稱,以方便生成相應的URL,舉例如下:

[Route("menu", Name = "mainmenu")]

public ActionResult MainMenu() { ... }

  你可以使用 Url.RouteUrl 來生成相應的 URL:

<a href="@Url.RouteUrl("mainmenu")">Main menu</a>

8、Area

  ASP.NET MVC 的 Area 概念對組織大型Web應用程式很有幫助,在Attribute路由中當然少不了對它的支援,只要使用 [RouteArea],就可以把 Controller 歸屬到某一個 Area 下,這時你可以放心的刪除 Area 下的 AreaRegistration 類了:

[RouteArea("Admin")]

[RoutePrefix("menu")]

[Route("{action}")]

public class MenuController : Controller

{

   // 匹配: /admin/menu/login

   public ActionResult Login() { ... }

   // 匹配: /admin/menu/show-options

   [Route("show-options")]

   public ActionResult Options() { ... }

   // 匹配: /stats

   [Route("~/stats")]

   public ActionResult Stats() { ... }

}

  現在你可以和以往的版本一樣使用 "Admin" Area,下面的程式碼會生成 URL "/Admin/menu/show-options":

Url.Action("Options", "Menu", new { Area = "Admin" })

  你還可以通過 AreaPrefix 來設定 Area 字首,例如:

[RouteArea("BackOffice", AreaPrefix = "back-office")]

  如果你同時使用 Attribute、AreaRegistration 類這兩種方式來註冊 Area 的話,你應該在註冊 Attribute 路由和傳統路由對映之間使用 AreaRegistration 註冊 Area,原因很簡單,路由註冊順序必須是從最精確的匹配規則開始再到普通的匹配規則,最後才是模糊的匹配規則,這樣就避免了在進行路由匹配時,過早的匹配了模糊規則,而相對精確的匹配起不到任何作用。下面的例子展示了這一點:

public static void RegisterRoutes(RouteCollection routes)

{

   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

   routes.MapMvcAttributeRoutes();

   AreaRegistration.RegisterAllAreas();

   routes.MapRoute(

       name: "Default",

       url: "{controller}/{action}/{id}",

       defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

   );

}