[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]):)
结论
这也标志着我们这个主题的结束。我已尽力涵盖了关于特性路由的所有相关主要内容,但仍有一些小型技巧和窍门未在此讨论。我将这些小细节留给你。希望你能自己弄清楚这些细节。祝你编码愉快!:)

