路由是ASP.NET MVC如何将URL与动作匹配。MVC 5支持一种新的路由,称为属性路由。顾名思义,属性路由使用属性来定义路由。属性路由使您对Web应用程序中的URL有更多的控制权。
早期的路由方式,称为基于约定的路由,仍然完全支持。事实上,您可以在同一个项目中组合这两种技术。
本文将介绍ASP.NET MVC 5中属性路由的基本特征和选项。
- 为什么属性路由?
- 启用属性路由
- 可选URL参数和默认值
- 路由前缀
- 默认路由
- 路由约束
自定义路由约束 - 路由名称
- 区域
一、为什么属性路由
例如,一个社会化增强的电子商务网站可以有以下路线:
{productId:int}/{ProductTitle}
Mapped to
ProductsController.Show(int id)
{username}
Mapped to
ProfilesController.Show(string username)
{username}/catalogs/{catalogId:int}/{catalogTitle}
Mapped to
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与操作之间的映射。前面的路由定义将使用以下简单的属性来设置:
[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参数和默认值
可以通过向路由参数添加问号来选择URL参数。还可以使用窗体参数=值指定默认值
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和/./1430210079都将路由到“View”操作,前者将列出所有图书,后者将列出特定图书。/books/lang
and /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) { … }
}
可以使用[RouTePiFix]属性设置整个控制器的公共前缀:
[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) { … }
}
如果需要的话,在方法属性上使用一个TrdE(~)来重写路由前缀:
[RoutePrefix(“reviews”)]
public class ReviewsController : Controller
{
// eg.: /spotlight-review
[Route(“~/spotlight-review”)]
public ActionResult ShowSpotlight() { … }
…
}
五、默认路由
还可以在控制器级别上应用[Route]属性,将操作捕获为参数。然后,该路由将应用于控制器中的所有操作,除非在特定操作上定义了特定的[Route],从而覆盖控制器上的默认设置
[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) { … }
}
六、路由约束
路由约束使您可以限制路由模板中的参数是如何匹配的。一般语法是{参数:约束}。例如:
// eg: /users/5
[Route(“users/{id:int}")]
public ActionResult GetUserById(int id) { … }
// eg: users/ken
[Route(“users/{name}”]
public ActionResult GetUserByName(string name) { … }
这里,如果URL的“ID”段是整数,则只选择第一条路径。否则,将选择第二条路线。
下表列出了所支持的约束。
约束 | 描述 | 示例 |
---|---|---|
alpha | 匹配大写或小写拉丁字母字符(A z,A z) | {x:alpha} |
bool | 匹配布尔值 | {x:bool} |
datetime | 匹配DateTime值 | {x:datetime} |
decimal | 匹配decimal值 | {x:decimal} |
double | 匹配一个64位浮点值 | {x:double} |
float | 匹配32位浮点值 | {x:float} |
guid | 匹配GUID值 | {x:guid} |
int | 匹配32位整数值 | {x:int} |
length | 在指定长度范围内匹配具有指定长度的字符串 | {x:length(6)} {x:length(1,20)} |
long | 匹配一个64位整数值 | {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: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>
八、区域
可以使用[RoutArea]属性定义控制器属于一个区域。这样做时,您可以安全地删除该区域的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() { … }
}
使用此控制器,字符串将产生以下链接生成调用“/Admin/menu/show-options“
Url.Action(“Options”, “Menu”, new { Area = “Admin” })
您可以通过使用名为参数的 AreaPrefix
设置用于从区域名称推迟的区域的自定义前缀,例如:
[RouteArea(“BackOffice”, AreaPrefix = “back-office”)]
如果同时使用带有路由属性的Area和基于约定的路由的区域(由AreaRegistration类设置),则需要确保在配置MVC属性路由之后进行区域注册,但是在设置默认的基于约定的路由之前。原因在于,路由注册应该从最特定的(属性)排序到更一般的(区域注册)到雾一般的(默认路由),以避免通用路由在流水线中太早地匹配传入的请求,从而“隐藏”更具体的路由。
示例:
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 }
);
}