[Attribute] ASP.NET MVC 5 / WebAPI 2 中的路由






4.94/5 (30投票s)
[Attribute] ASP.NET MVC 5 / WebAPI 2 中的路由
引言
在上一篇文章中,我讨论了路由框架是如何工作的。在这篇文章中,我将讨论 MVC5 和 WebAPI2 提供的一项最酷的最新功能,它被称为“Attribute Routing”(特性路由)。在底层,特性路由仍然保持着路由框架的相同机制。
那么,你现在可能在想,如果新的路由功能(特性路由)在底层也使用相同的路由机制,那么它到底有什么不同之处呢?嗯,在过去几年开发大型企业 Web 应用的过程中,人们发现随着项目变大并且特殊情况不断累积,在一个文件中跟踪所有路由变得很困难。当开发人员需要编写代码来应用复杂的约束时,事情会变得有些混乱。在大多数情况下,他们会通过实现 IRouteConstraint
并定义 Match
方法中的自定义逻辑来使用自定义约束——如果返回 true
,则该路由匹配。
public interface IRouteConstraint
{
bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
}
基于约定的路由(Convention-based Routing)的问题在于,由于路由在物理上与其应用的控制器分离,因此通常需要一些侦探工作才能理解它们之间的关系。为了更好地解决这个问题,#Microsoft 从 Tim McCall 那里采纳了 ASP.NET MVC5 和 WebApi2 中的“特性路由”功能。
顾名思义,特性路由使用特性来定义路由,并且可以用于控制器操作甚至控制器类。简而言之,我将解释这些功能如何让生活更轻松。但在此之前,你需要在你的解决方案中启用此新功能。所以,我们先来做这个。
启用特性路由
要启用特性路由,我们需要在配置期间调用路由集合类中的 MapMvcAttributeRoutes
方法。
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Add the following line of code
routes.MapMvcAttributeRoutes();
//[Code Excerpt]
}
}
如前所述,你可以将“基于约定的路由”和“[特性]路由”保留在同一个 Web 应用中,如果是这样,请牢记以下说明。
MapMvcAttributeRoutes() 必须在基于约定的路由之前调用。
现在,一旦我们启用了特性路由,让我们来看一些例子。
示例
定义路由
路由特性必须定义在操作方法顶部或控制器顶部。在以下示例中,我在名为“About()
”的操作方法顶部声明了特性路由。
public class HomeController : Controller
{
[Route("Users/about")]
public ActionResult About()
{
ViewBag.Message = "You successfully reached USERS/About route";
return View();
}
}
定义通用前缀
如果你想为整个控制器指定一个通用前缀,那么与其在每个操作的顶部都指定 [RoutePrefix]
特性,不如在控制器级别指定它。
[RoutePrefix("Movie")]
public class HomeController : Controller
{
//Route: Movie/Index
public ActionResult Index()
{
ViewBag.Message = "You are in Home Index";
return View();
}
//Route: Movie/About
public ActionResult About()
{
ViewBag.Message = "You successfully reached USERS/About route";
return View();
}
}
重写通用前缀
有时你可能不希望你的操作在相同的路由前缀下响应。在这些情况下,你必须重写通用的路由前缀。这样做很简单。
使用波浪线 (~) 在方法特性上重写路由前缀。
[RoutePrefix("Movie")]
public class HomeController : Controller
{
//Route: Movie/Index
[Route]
public ActionResult Index()
{
ViewBag.Message = "You are in Home Index";
return View();
}
//Route: NewRoute/About
[Route("~/NewRoute/About")]
public ActionResult About()
{
ViewBag.Message = "You successfully reached NEWRoute/About route";
return View();
}
}
定义默认路由
现在你知道如何定义通用前缀以及在需要时如何重写它。我们的下一个目标是使用特性路由定义默认路由。首先在控制器级别应用 [Route]
特性,然后指定一个默认操作作为参数。
在以下代码片段中,如果未指定任何内容,路由框架会将用户导向“/Movie/index”路由。
[RoutePrefix("Movie")]
[Route("{action=index}")]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "You are in Home Index";
return View();
}
//Route: Movie/About
public ActionResult About()
{
ViewBag.Message = "You successfully reached USERS/About route";
return View();
}
}
注意:要重写默认路由, 只需在特定操作上指定特定路由!
可选 URI 参数
要使任何参数可选,请在 Route
参数后添加一个问号。
public class HomeController : Controller
{
[Route("home/{id?}")]
public ActionResult Employee(int id)
{
ViewBag.Message = "You are in Home Index";
return View();
}
}
URI 参数中的默认值
要将任何参数指定为默认值,请在路由参数中初始化一个值。
public class HomeController : Controller
{
[Route("home/{type=en}")]
public ActionResult Search(string type)
{
ViewBag.Message = "You are in Home Index";
return View();
}
}
施加约束
施加约束会限制路由模板中的参数如何匹配。上面,我已经向你展示了如何通过 ID 搜索特定员工。现在,假设我们也希望最终用户按 Name
搜索员工。另外,假设在这种情况下会渲染不同的视图。
home/3 <–render “View A”
home/shahriar <–render “View B”
我们可以做什么呢?我们可以添加另一个 ActionResult
,它接收一个 string
参数。但仅这样做并不能解决我们的问题。我们必须明确定义和施加某些约束,才能让路由框架完美地工作。
仔细看路由。在这两种情况下,路由框架都会命中 home 控制器,然后根据收到的参数渲染不同的视图。如果我们不明确告诉路由框架施加某些约束,它就无法将你(查看者)正确地导向相应操作。
施加约束的一般语法是 {parameter:constraint}
。
public class HomeController : Controller
{
[Route("home/{id? : int}")]
public ActionResult Employee(int id)
{
//Logic goes here
}
[Route("home/{name}")]
public ActionResult Employee(string name)
{
//Logic goes here
}
}
在这里,只有当 URI 的“id
”部分是整数时,第一个路由才会被选中。否则,将选择第二个路由。(“?”)符号表示该参数是可选的。
Ken Egozi 发布了一篇关于 特性路由 的好文章,他在其中发布了一份包含一些示例的完整约束列表,我已将其添加到下面。
约束 | 描述 | 示例 |
alpha | 匹配大写或小写的拉丁字母字符 (a-z, A-Z) | {x:alpha} |
bool | 匹配布尔值。 | {x:bool} |
datetime | 匹配 DateTime 值。 | {x:datetime} |
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
”、“maxlength
”、“minlength
”,会接受括号内的参数。- 要对参数应用多个约束,请用冒号分隔每个约束。
限制特定操作的 HTTP 动词
允许使用现有特性(例如 [HttpGet]
、[HttpPost]
和 [AcceptVerbs]
):)
结论
这也标志着我们这个主题的结束。我已尽力涵盖了关于特性路由的所有相关主要内容,但仍有一些小型技巧和窍门未在此讨论。我将这些小细节留给你。希望你能自己弄清楚这些细节。祝你编码愉快!:)