【Web API系列教程】2.2 — ASP.NET Web API中的路由和动作选择机制

简介: 这篇文章描述了ASP.NET Web API如何将HTTP请求路由到控制器上的特定动作。备注:想要了解关于路由的高层次概述,请查看Routing in ASP.NET Web API。

这篇文章描述了ASP.NET Web API如何将HTTP请求路由到控制器上的特定动作。

备注:想要了解关于路由的高层次概述,请查看Routing in ASP.NET Web API。

这篇文章侧重于路由过程的细节。如果你创建了一个Web API项目并且发现一些请求并没有按你预期得到相应的路由,希望这篇文章有所帮助。

路由有以下三个主要阶段:

  1. 将URI匹配到路由模板
  2. 选择一个控制器
  3. 选择一个动作

你可以用自己的习惯行为来替换其中一些过程。在本文中,我会描述默认行为。在结尾,我会指出你可以自定义行为的地方。

路由模板(Route Templates)

路由模板看起来和URI路径非常相似,但是它能包含用大括号指明的占位符。

"api/{controller}/public/{category}/{id}"

当你创建了一个路由,你为一些或全部占位符提供默认的值:

defaults: new { category = "all" }

你也可以提供一些约束(constraints),它限制了URI字段如何才能匹配一个占位符:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.

框架会尽力将URI路径中的字段匹配到模板中。模板中的文字必须准确匹配。一个占位符可以匹配多个变量,除非你指定了约束。框架不会匹配URI的其他部分,比如主机名或查询参数。框架仅仅在用于匹配URI的路由表中选择第一个路由。

这里有两个特殊的占位符:”{controller}“和“{action}”。

  • “{controller}“提供了控制器的名称。
  • “{action}“提供了动作的名称。在Web API中,通过会忽略“{action}”。

Defaults

如果你提供了默认的API,路由将会匹配缺少这些的URI。例如:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}",
    defaults: new { category = "all" }
);

对于URI http: //localhost/api/products 将会匹配这个路由。{category} 字段会被分配默认值 all

路由字典(Route Dictionary)

如果框架发现了URI的一个匹配,它会创建一个包含了每个占位符适用的值的字典集合。键是不包含大括号的占位符名称。值是提取自URI路径或者默认表单。该字典被存储在IHttpRouteData对象中。

在路由匹配阶段,“{controller}“和”{action}“占位符会被像其他占位符一样对待。它们被同其他值一起简单地存储在字典中。

对于defaults,它可以有一个特殊值RouteParameter.Optional。如果一个占位符被分配到这个值,那么这个值不会被添加到路由字典中。例如:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}/{id}",
    defaults: new { category = "all", id = RouteParameter.Optional }
);

对于URI路径“api/products”,路由字典将会包含:

  1. controller:“products”
  2. category:“all”

然而对于“api/products/toys/123”,路由字典将会包含:

  1. controller:“products”
  2. category:“all”
  3. id:“123“

对于defaults,它同样也会包含一个没有在路由模板中任何地方出现的值。如果路由匹配了,这个值会被存储在字典中。例如:

routes.MapHttpRoute(
    name: "Root",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "customers", id = RouteParameter.Optional }
);

如果URI路径“api/root/8”,字典将会包含两个值:

  • controller:”customers“
  • id:“8”

选择控制器(Selecting a Controller)

控制器的选择由IHttpControllerSelector.SelectController方法来处理。这个方法需要传入一个HttpRequestMessage实例并返回HttpControllerDescriptor对象。默认的实现是由DefaultHttpControllerSelector类来实现的。这个类使用了一个简单的算法:

  1. 在路由字典中查找键”controller“。
  2. 提取出这个键对应的值,并添加字符串“Controller”以得到控制器的类型名
  3. 用这个类型名来查找一个Web API控制器

例如,如果路由字典包含键值对“controller”=“products”,那么控制器类型就是“ProductsController”。如果这里不存在匹配的类型,或存在多个匹配,那么框架就会向客户端发送一个错误。

对于步骤3,DefaultHttpControllerSelector会使用IHttpControllerTypeResolver接口来得到Web API控制器类型的列表。IHttpControllerTypeResolver的默认实现会返回(a)实现IHttpController,(b)不是抽象的,(c)名称以“Controller“结尾的所有公共的类。

动作选择

在选择控制器之后,框架会通过调用IHttpActionSelector.SelectAction方法来选择动作。这个方法需要传入一个HttpControllerContext参数以及返回一个HttpActionDescriptor对象。

默认的实现由ApiControllerActionSelector类来提供。为了选择一个动作,它会按以下要求来查找:
1) 请求的HTTP方法
2) 路由模板中的“{action}“占位符(如果存在)
3) 控制器中动作的参数

在查看选择算法之前,我们需要理解关于控制器动作的一些东西。

控制器中的哪些方法会被认为是“动作“?当选择一个动作时,框架仅仅在控制器中查找公共的实例方法。当然了,它会排除一些”特殊“的方法(构造函数,事件,操作重载等等)和继承自ApiController类的方法。

HTTP方法。框架只会选择匹配请求的HTTP方法的动作,它取决于以下几点:

  1. 你可以用某个属性来具体说明是HTTP方法:AcceptVerbs,HttpDelete,HttpGet,HttpHead,HttpOptions,HttpPatch,HttpPost或HttpPut。
  2. 或者,如果一个控制器方法的名称以”Get”,“Post“,”Put“,”Delete“,”Head“,”Options“或”Patch“开始,那么按照约定该动作就支持HTTP方法。
  3. 如果不包含以上几点,但支持POST的方法。

参数绑定。参数绑定是指Web API如何如何为参数创建一个值。这里是参数绑定的默认规则:

  1. 简单类型直接从URI中提取
  2. 复杂类型从请求体重提取

简单类型包括所有.NET框架基本类型(.NET Framework primitive types),再加上DateTime、Decimal、Guid、String和TimeSpan。对于每个动作,最多有一个参数可以读取请求体。

备注:重载默认绑定规则也是有可能的。查看WebAPI Parameter binding under the hood.

有了以上这些背景知识,这里是动作选择的算法:

  1. 基于HTTP请求方法匹配到的控制器创建一个动作列表。
  2. 动作路由字典包含“action“记录,移除其名字不匹配该值的动作。
  3. 根据如下规则,尽力将动作参数匹配到URI:

    • a 对于每个动作,当绑定从URI中获得参数时得到一个简单类型的参数列表。执行可选的参数。
    • b 从这个列表中,无论是在路由字典中还是URI查询字符串中,都尽力找出针对每个参数名称的匹配。匹配不区分大小写并且不取决于参数顺序。
    • c 当列表中的每个参数在URI中都有一个匹配时,选择一个动作。
    • d 如果多个动作符合这些标准,那么选择其中一个有最多参数匹配的。
  4. 忽略包含[NonAction]属性的动作。

步骤3可能是最容易迷惑的。基本的思想是参数可以从URI、请求体或绑定中获得它的值。对于来自URI的参数,我们会确保URI确实包含一个给参数的值,不论是在路径(通过路由字典)还是在查询字符串中。

例如,考虑如下动作:

public void Get(int id)

这个id参数绑定到URI上,因此,这个动作可以匹配到包含一个给“id“的值的URI,不论是在路由字典还是查询字符串中。

可选参数是个例外,因为它们是可选的。对于可选参数,如果这个绑定不了从URI中得到这个值也是没关系的。

因为一些不同的原因,复杂类型也是个例外。复杂类型只能通过自定义绑定来绑定到URI上。但是在这种情况下,框架无法事先知道参数可能被绑定到一个特殊的URI。为了弄清楚它,就需要去执行这个绑定。这个选择算法的目标是在执行任何绑定之前,从静态描述中去选择一个动作。因此,复杂类型会从这种匹配算法中执行。

在动作被选取好了,所有的参数绑定也就被执行了。

总结:

  1. 动作必须匹配请求的HTTP方法。
  2. 动作名(如果存在)必须匹配路由字典中的“action“词条
  3. 对于动作的所有参数,如果参数提取自URI,那么参数名必须在路由字典或URI查询字符串中被找到。(可选参数和复杂类型的参数除外。)
  4. 尽量去匹配最多的参数数目。但最好的匹配也可能是不包含任何参数的方法。

扩展示例(Extended Example)

路由:

routes.MapHttpRoute(
    name: "ApiRoot",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "products", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

控制器:

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAll() {}
    public Product GetById(int id, double version = 1.0) {}
    [HttpGet]
    public void FindProductsByName(string name) {}
    public void Post(Product value) {}
    public void Put(int id, Product value) {}
}

HTTP请求:

GET http://localhost:34701/api/products/1?version=1.5&details=1

路由匹配(Route Matching)

该URI会匹配到名为”DefaultApi”的路由。这个路由字典包含以下词条:

  • controller:“products”
  • id:“1“

这个路由字典不包含查询字符串“version”和“details”,但是在动作选择的时候这些仍然会被考虑。

控制器选择(Controller Selection)

根据路由字典中的“controller”词条,控制器类型是ProductsController。

动作选择(Action Selection)

该HTTP请求是一个GET请求。相应的支持GET的控制器动作是GetAll、GetById和FindProductsByName。路由字典中不包含任何“action“词条,所以我们不用去匹配动作名称。

接下来,我们尝试着匹配动作的参数名称,现在仅在GET动作中查找。

Action Parameters to Match
GetAll none
GetById “id”
FindProductsByName “name”

注意到GetById的version参数没有被考虑,因为它是一个可选参数。

显而易见GetAll方法能够匹配,GetById方法也能匹配,因为路由字典中包含“id“。FindProductsByName方法不匹配。

最后是GetById方法获胜,因为它能够匹配到一个参数,相对应的是没有参数能匹配GetAll。该方法伴随以下参数的值来执行:

  • id = 1
  • version = 1.5

注意到尽管version参数没有在选择算法中使用,但该参数的值也依旧是来自URI的查询字符串中。

扩展点(Extension Points)

Web API为路由过程的一些部分提供了扩展点。

Interface Description
IHttpControllerSelector Selects the controller.
IHttpControllerTypeResolver Gets the list of controller types. The DefaultHttpControllerSelector chooses the controller type from this list.
IAssembliesResolver Gets the list of project assemblies. The IHttpControllerTypeResolverinterface uses this list to find the controller types.
IHttpControllerActivator Creates new controller instances.
IHttpActionSelector Selects the action.
IHttpActionInvoker Invokes the action.

为任何这些接口提供自己的实现,请使用HttpConfiguration对象上的Services集合:

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));
目录
相关文章
|
6天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
21 4
|
8天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
43 3
|
2天前
|
监控 负载均衡 API
Web、RESTful API 在微服务中有哪些作用?
在微服务架构中,Web 和 RESTful API 扮演着至关重要的角色。它们帮助实现服务之间的通信、数据交换和系统的可扩展性。
9 2
|
17天前
|
前端开发 JavaScript API
惊呆了!学会AJAX与Fetch API,你的Python Web项目瞬间高大上!
在Web开发领域,AJAX与Fetch API是提升交互体验的关键技术。AJAX(Asynchronous JavaScript and XML)作为异步通信的先驱,通过XMLHttpRequest对象实现了局部页面更新,提升了应用流畅度。Fetch API则以更现代、简洁的方式处理HTTP请求,基于Promises提供了丰富的功能。当与Python Web框架(如Django、Flask)结合时,这两者能显著增强应用的响应速度和用户体验,使项目更加高效、高大上。
34 2
|
19天前
|
前端开发 API 开发者
从零到精通,AJAX与Fetch API让你的Python Web前后端交互无所不能!
从零到精通,AJAX与Fetch API让你的Python Web前后端交互无所不能!
32 3
|
28天前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
5天前
|
移动开发 前端开发 JavaScript
前端开发实战:利用Web Speech API之speechSynthesis实现文字转语音功能
前端开发实战:利用Web Speech API之speechSynthesis实现文字转语音功能
29 0
|
1月前
|
开发框架 前端开发 .NET
VB.NET中如何利用ASP.NET进行Web开发
在VB.NET中利用ASP.NET进行Web开发是一个常见的做法,特别是在需要构建动态、交互式Web应用程序时。ASP.NET是一个由微软开发的开源Web应用程序框架,它允许开发者使用多种编程语言(包括VB.NET)来创建Web应用程序。
47 5
|
1月前
|
开发框架 JSON 缓存
震撼发布!Python Web开发框架下的RESTful API设计全攻略,让数据交互更自由!
在数字化浪潮推动下,RESTful API成为Web开发中不可或缺的部分。本文详细介绍了在Python环境下如何设计并实现高效、可扩展的RESTful API,涵盖框架选择、资源定义、HTTP方法应用及响应格式设计等内容,并提供了基于Flask的示例代码。此外,还讨论了版本控制、文档化、安全性和性能优化等最佳实践,帮助开发者实现更流畅的数据交互体验。
57 1
|
17天前
|
前端开发 API 数据格式
颠覆传统!AJAX、Fetch API与Python后端,开启Web开发新篇章!
在Web开发领域,技术的快速迭代推动着应用不断进化。传统前后端交互方式已无法满足现代Web应用对高效、实时性和用户体验的需求。AJAX作为异步通信的先驱,使页面无需刷新即可更新部分内容,显著提升用户体验;尽管XML曾是其主要数据格式,但如今JSON已成为主流。Fetch API则以其简洁、灵活的特点成为AJAX的现代替代品,基于Promises的异步请求让开发更加高效。与此同时,Python后端凭借高效稳定和丰富的库支持,成为众多开发者的首选,无论是轻量级的Flask还是全功能的Django,都能为Web应用提供强大的支撑。
29 0