[水煮 ASP.NET Web API2 方法论](3-1)集中式路由

问题

怎样集中的定义路由

 

解决方案

通过调用 HttpRouteCollectionExtension 类中的 MapHttpRoute 扩展方法在 HttpRouteCollection 中定义路由,可以通过 HttpConfiguration 对象调用。

最基础的使用就是定义一个非常通用的路由模板,他会通过 {controller} 占位符匹配所有的 Controller。如代码片段 3-1 所示。

 

代码片段 3-1. ASP.NET WEB API 默认定义的路由以及一个简单的 Controller

 1 config.Routes.MapHttpRoute(
 2     name: "DefaultApi",
 3     routeTemplate: "api/{controller}/{id}",
 4     defaults: new {id = RouteParameter.Optional}
 5     );
 6 
 7  
 8 
 9 public class OrdersController : ApiController
10 {
11     public Order Get(int id)
12     {
13         // 忽略逻辑
14     }
15 }

 

在路由模板中,可以定义自己的占位符,如代码片段 3-1 所示的 {id}。他与 Action 中的参数名称相匹配匹配,ASP.NET WEB API 会从 request 中提取出相应的值,传入 Action 方法中。也就是,代码片段 3-1 中 OrdersController 的 Get请求方法的这种情

况。

 

工作原理

从最初版本开始,ASP.NET WEB API 就一直使用集中式路由维护路由表,这和 MVC 如出一辙。

ASP.NET WEB API 定义了很多 MapHttpRoute 的变种。所需参数最少的一个方法,只需要一个路由模板和一个路由名称。

1 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name,
2     string routeTemplate)

 

除声明简单基础路由以外的方式,也可以通过默认值和约束,或者设置每个路由消息处理程序,将会在下面的章节介绍这个。所有的这些操作都是通过 Map HttpRoute 的重载方法实现的。

1 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name,
2     string routeTemplate, object defaults)
3  
4 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name,
5     string routeTemplate, object defaults, object constraints)
6  
7 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name,
8     string routeTemplate, object defaults, object constraints, HttpMessageHandler handler)

 

通过路由默认值,可以直接定位到路由相关的 Controller;可是,这样的特定路由需要定义在通用路由之前,也就是代码片段 3-1 的路由之前。原因就是,路由是顺序匹配的,路由在选定处理一个 HTTP 请求的时候,是在每次请求的时候,扫描路由集合中的所有路由,选用第一个被匹配的路由模板。简单的说,越是特殊的路由定义越靠前,越是通用的路由定义越靠后。

 

在  Self-hosting 中,ASP.NET WEB API 是以 IDictionary<stirng, IHttpRoute> 的形式在维护路由,Idictionary<string, IHttpRoute> 是在 HttpRouteCollection 类中。在 Web host 中也提供了 System.Web.Routing.RouteCollection 的扩展方法,因此,可以直接在这个里面定义 ASP.NET WEB API 路由。

RouteTable.Routes.MapHttpRoute("DefaultApi", "api/{controller}")

 

如果使用 ASP.NET 运行时 host WEB API,无论使用什么扩展方法声明路由,都会被添加到同一个 RouteTable 中。内部是通过一个叫做 HostedHttpRouteCollection 的类来实现的,这个类是 HttpRouteCollection 的子类,而不是在字典(比如 self-host)中维护路由,所有转发路由都是查找 System.Web.RouteCollection。

 

ASP.NET WEB API 与 ASP.NET MVC 是不一样的,API 使用的是基于 HTTP 谓词匹配来处理一个 Controller 请求。换句话说,框架会根据 HTTP 的东西来选择一个 Action 处理请求,选择的逻辑如下:

  • 通过方法名推断 HTTP 谓词,如果名字类似于 PostOrder、GetById 等等。
  • 通过 Action 属性推断 HTTP 谓词。
  • Action 参数定义与路由模板必须匹配。

 

典型的 ASP.NET WEB API 路由是指向资源的。可以通过 HTTP  谓词调用,还需要除了 Action 之外的参数。如代码片段 3-1 所示的默认路由,可以匹配如下请求:

  • GET myapi.com/api/orders
  • POST myapi.com/api/inovice
  • PUT myapi.com/api/invice/2

 

小提示 在 ASP.NET WEB API 是可以使用 RPC 的,具体细节会在 3-6 介绍。

 

代码演示

ASP.NET WEB API 是基于 HTTP 谓词进行分发逻辑的,不像 ASP.NET MVC,很容易出现 AmbiguousMatchEception。例如,可以设想一些使用代码片段 3-1 的路由的例子。

如代码片段 3-2 所示的例子,这三个方法都是可以处理相同的 GET 请求,如,/api/orders/1.

代码片段 3-2.

 

代码片段 3-2. ASP.NET WEB API Controller

 1 public class BadOrdersController : ApiController
 2 {
 3     [HttpGet]
 4     public Order FindById(int id)
 5     {
 6         // 忽略逻辑
 7     }
 8  
 9     public Order GetById(int id)
10     {
11         // 忽略逻辑
12     }
13  
14     public Order Get(int id)
15     {
16         // 忽略逻辑
17     }
18 }

 

同时我们需要注意,定义复杂、多等级、嵌套路由的时候,集中式路由变得有点麻烦。考虑如下路由

  • GET myapi.com/api/teams
  • GET myapi.com/api/teams/1
  • GET myapi.com/api/teams/1/players

 

使用集中式路由,可以在 Controller 中使用如下三个方法;然而,我们必须注意路由之间冲突的问题,因为有两个 GET 方法都是只使用了一个 int 的参数。如代码片段 3-3 所示。有一个特殊的路由指出了 URI 中包含 /players/ 段会被匹配到,而且这个路由定义在通用路由之前。

 

代码片段 3-3 使用集中式路由配置嵌套路由

 1 public class TeamsController : ApiController
 2 {
 3     public Team GetTeam(int id)
 4     {
 5         // 忽略逻辑
 6     }
 7     public IEnumerable<Team> GetTeams()
 8     {
 9         // 忽略逻辑
10     }
11     public IEnumerable<Player> GetPlayers(int teamId)
12     {
13         // 忽略逻辑
14     }
15 }
16 
17  
18 
19 config.Routes.MapHttpRoute(
20     name: "players",
21     routeTemplate: "api/teams/{teamid}/players",
22     defaults: new {controller = "teams"}
23     );
24 config.Routes.MapHttpRoute(
25 name: "DefaultApi",
26 routeTemplate: "api/{controller}/{id}",
27 defaults: new { id = RouteParameter.Optional }
28 );

 

集中式路由的主要问题是,特殊路由的定义,仅仅是处理特殊的 Controller 特殊的 Action。通过定义特殊路由并添加到一般路由之前,在人为干预下短路了路由匹配。

 

这种处理并不是最理想,当应用程序变大之后,会有更复杂的、多层级的路由,可能就要在集中式路由中痛苦的挣扎(路由维护和调试)。更好的选择就是使用直接式路由,下一篇 3-2 定义直接路由,以及后面介绍路由的时候都会涉及直接式路由。

 

posted @ 2016-11-24 08:09  水煮Code  阅读(6338)  评论(0编辑  收藏  举报