65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (30投票s)

2014年5月19日

CPOL

5分钟阅读

viewsIcon

186132

[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}$)}

请密切注意

  1. 一些约束,例如“min”、“maxlength”、“minlength”,会接受括号内的参数。
  2. 要对参数应用多个约束,请用冒号分隔每个约束。

限制特定操作的 HTTP 动词

允许使用现有特性(例如 [HttpGet][HttpPost][AcceptVerbs]:)

结论

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

额外资源

  1. Maarten Balliauw 的博客
  2. Web api 2 中的特性路由
  3. http://benfoster.io/blog/improving-aspnet-mvc-routing-configuration
© . All rights reserved.