asp.net mvc中的路由是如何去匹配URL的呢?
mvc5支持一种新的路由方式,叫做特性路由。顾名思义,特性路由就是利用特性去定义路由。特性路由让你在应用程序中能更好的控制URL。
早期的路由方式,叫做传统路由,现在也是完全支持的。实际上,你可以将两种技术结合在同一个项目当中。
这篇文章将会涵盖asp.net mvc5中特性路由所有的基本用法。
为什么要特性路由?
举个例子,一个电子商务网站会有以下路由:
{productId:int}/{productTitle}
对应 ProductsController.Show(int id){username}
对应 ProfilesController.Show(string username){username}/catalogs/{catalogId:int}/{catalogTitle}
对应 CatalogsController.Show(string username, int catalogId)在以前的asp.net mvc版本中,这个路由规则在 RouteConfig.cs文件中进行设置,让它指向正确的控制器和方法,如:
`
routes.MapRoute(
name: “ProductPage”,
url: “{productId}/{productTitle}”,
defaults: new { controller = “Products”, action = “Show” },
constraints: new { productId = “\\d+” }
);
`
当路由定义的方法都来源于一个控制器中,此时我们可以使用特性路由进行简单化处理,特性路由可更容易的看清URL和action之间的映射。如下设置:
[Route(“{productId:int}/{productTitle}”)]
public ActionResult Show(int productId) { … }
开启特性路由
只需要在路由配置中调用此方法:MapMvcAttributeRoutes
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
routes.MapMvcAttributeRoutes();
}
}
你也可以结合特性路由和传统路由:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: “Default”,
url: “{controller}/{action}/{id}”,
defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
);
}
可选参数和默认值
你可以通过使用?来设置URL可选参数,也可以使用parameter=value这中指定型的写法。
public class BooksController : Controller
{
// eg: /books
// eg: /books/1430210079
[Route(“books/{isbn?}”)]
public ActionResult View(string isbn)
{
if (!String.IsNullOrEmpty(isbn))
{
return View(“OneBook”, GetBook(isbn));
}
return View(“AllBooks”, GetBooks());
}
// eg: /books/lang
// eg: /books/lang/en
// eg: /books/lang/he
[Route(“books/lang/{lang=en}”)]
public ActionResult ViewByLanguage(string lang)
{
return View(“OneBook”, GetBooksByLanguage(lang));
}
}
在这个例子中, /books 和/books/1430210079 都是匹配的一个方法和视图,但前者会列举出所有的书籍,而后者只会列举出特定的书籍。 /books/lang 和 /books/lang/en 都是此理。
路由前缀
在控制器中会使用一样的前缀,例如:
public class ReviewsController : Controller
{
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
}
你可以设置一个统一的前缀利用[RoutePrefix]这个特性即可。
[RoutePrefix(“reviews”)]
public class ReviewsController : Controller
{
// eg.: /reviews
[Route]
public ActionResult Index() { … }
// eg.: /reviews/5
[Route(“{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg.: /reviews/5/edit
[Route(“{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
}
也可利用此符号 (~) 在路由特性中标识省略前缀,如:
[RoutePrefix(“reviews”)]
public class ReviewsController : Controller
{
// eg.: /spotlight-review
[Route(“~/spotlight-review”)]
public ActionResult ShowSpotlight() { … }
…
}
默认路由
你也可以在控制器上利用[Route] 属性,将action作为参数捕获。然后,该路由特性将应用于控制器中的所有action,除非在特定的操作上定义了特定的特性路由,覆盖了控制器上的默认设置。
[RoutePrefix(“promotions”)]
[Route(“{action=index}”)]
public class ReviewsController : Controller
{
// eg.: /promotions
public ActionResult Index() { … }
// eg.: /promotions/archive
public ActionResult Archive() { … }
// eg.: /promotions/new
public ActionResult New() { … }
// eg.: /promotions/edit/5
[Route(“edit/{promoId:int}”)]
public ActionResult Edit(int promoId) { … }
}
路由约束
路由约束 可以让你更严格的去限制这个路由参数是否匹配. 其语法为:{parameter:constraint}. 例子:
// eg: /users/5
[Route(“users/{id:int}”]
public ActionResult GetUserById(int id) { … }
// eg: users/ken
[Route(“users/{name}”]
public ActionResult GetUserByName(string name) { … }
首先先进入第一个路由,判断其参数id是否为int类型,不是则进入下一个路由进行匹配。
mvc中默认的路由约束有:
Constraint | Description | Example |
---|---|---|
alpha | Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) | {x:alpha} |
bool | Matches a Boolean value. | {x:bool} |
datetime | Matches a DateTime value. | {x:datetime} |
decimal | Matches a decimal value. | {x:decimal} |
double | Matches a 64-bit floating-point value. | {x:double} |
float | Matches a 32-bit floating-point value. | {x:float} |
guid | Matches a GUID value. | {x:guid} |
int | Matches a 32-bit integer value. | {x:int} |
length | Matches a string with the specified length or within a specified range of lengths. | {x:length(6)} {x:length(1,20)} |
long | Matches a 64-bit integer value. | {x:long} |
max | Matches an integer with a maximum value. | {x:max(10)} |
maxlength | Matches a string with a maximum length. | {x:maxlength(10)} |
min | Matches an integer with a minimum value. | {x:min(10)} |
minlength | Matches a string with a minimum length. | {x:minlength(10)} |
range | Matches an integer within a range of values. | {x:range(10,50)} |
regex | Matches a regular expression. | {x:regex(^\d{3}-\d{3}-\d{4}$)} |
有些约束是需要注意的,如Min,这种约束可以复合使用。如:
// eg: /users/5
// but not /users/10000000000 because it is larger than int.MaxValue,
// and not /users/0 because of the min(1) constraint.
[Route(“users/{id:int:min(1)}”)]
public ActionResult GetUserById(int id) { … }
指定一个参数是可选的,使用?修饰符,它会在内敛约束判断之后再判断此参数是否可选。如:
// eg: /greetings/bye
// and /greetings because of the Optional modifier,
// but not /greetings/see-you-tomorrow because of the maxlength(3) constraint.
[Route(“greetings/{message:maxlength(3)?}”)]
public ActionResult Greet(string message) { … }
自定义路由约束
实现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
{
// eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
[Route(“temp/{scale:values(celsius|fahrenheit)}”)]
public ActionResult Show(string scale)
{
return Content(“scale is “ + scale);
}
}
路由名
可以为路由指定一个名字,这样就可以方便的生成URL。例如:
[Route(“menu”, Name = “mainmenu”)]
public ActionResult MainMenu() { … }
可以使用Url.RouteUrl方法来生成一个链接:
<a href=”@Url.RouteUrl(“mainmenu”)“>Main menu</a>
区域
[RouteArea]利用这个特性可以标识在区域下的控制器。如果使用了此特性,那么区域注册就可以不用了,直接移除AreaRegistration 类。
[RouteArea(“Admin”)]
[RoutePrefix(“menu”)]
[Route(“{action}”)]
public class MenuController : Controller
{
// eg: /admin/menu/login
public ActionResult Login() { … }
// eg: /admin/menu/show-options
[Route(“show-options”)]
public ActionResult Options() { … }
// eg: /stats
[Route(“~/stats”)]
public ActionResult Stats() { … }
}
在这个控制器中,使用Url.Action会生成这样的URL: /Admin/menu/show-options
如:
Url.Action(“Options”, “Menu”, new { Area = “Admin” })
假如想将区域设置一个别名,利用AreaPrefix 即可。如:
[RouteArea(“BackOffice”, AreaPrefix = “back-office”)]
在区域中假如想使用传统路由和特性路由,那么区域中的传统路由需要注册好,避免路由丢失。
如:
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 }
);
}