Web API路由和动作选择
前言
本文描述ASP.NET Web API如何把一个HTTP请求路由到控制器的一个特定的Action上。关于路由的总体概述可以参见上一篇教程 http://www.cnblogs.com/aehyok/p/3442051.html。这篇文章主要来学习路由过程的细节。如果你创建了一个Web API项目,发现有一些请求没有按照你期望的方式被路由,希望这篇文章将对你有所帮助。
本文主要分为三个阶段:
1.匹配URI到一个Route Template。
2.选择一个Controller。
3.选择一个Action。
你可以用自己的自定义行为来替换这一过程中的某些部分。在本文中,我将来描述默认的行为。在文章结尾,我会注明可以在什么地方自定义行为。
Route Templates
路由模版看上去类似于一个URI路径,但它可以具有占位符,并用花括号来指示:
"api/{controller}/public/{category}/{id}"
当创建一个路由的时候,你可以为某些或所有占位符提供默认值:
defaults: new { category = "all" }
你也可以提供约束,它限制URI片段如何与占位符匹配:
constraints: new { id = @"\d+" } // Only matches if "id" is one or more digits.
上面语句是通过正则表达式来限制片段的取值,上面的注释说明 id片段只匹配一个或多个数字,因此URI中的id片段必须是数字才能与这个路由进行匹配。
这个框架试图把URI路径中的片段与这个模板进行匹配。模板中的文字必须严格匹配。一个占位符可以匹配任何值,除非你指定了约束。这个框架不会匹配URI另外的部分,例如主机名或者一个查询字符串。这个框架会选择路由表中第一个匹配的路由。
这里有两个特殊的占位符:“{controller}”和“{action}”。
- “{controller}”提供控制器名。
- “{action}”提供动作名。在Web API中,通常的约定是忽略“{action}”的。
Defaults(默认值)
如果你提供默认值,那么这个路由将匹配缺少这些片段的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}" and "{action}"占位符的处理和其他占位符是一样的。它们用另外的值被简单的存储在字典中。
在默认值中可以使用特殊的RouteParameter.Optional值。如果一个占位符被赋予了这个值,那么这个值将不会被添加到路由字典中,例如:

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

对于URI路径“api/products”,路由字典将含有:controller:"products"、category:"all"。
然而,对于“api/products/toys/123”,路由字典将含有:controller:"products"、category:"toys"、id:"123"。
这个默认值也可以包含未出现在路由模板中的值。若这条路由匹配,则该值会被存储在路由字典中。例如:

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的默认实现会返回所有符合以下条件的public类:
a:实现IHttpController的类。
b:是非抽象类。
c:名称以“Controller”结尾的类。
Action Selection
选择了控制器之后,这个框架会通过调用IHttpActionSelector.SelectAction方法来选择动作。这个方法以HttpControllerContext为参数,并返回HttpActionDescriptor。
这个默认实现是由ApiControllerActionSelector类提供的。为了选择一个动作,会查找以下方面:
1.HTTP请求的方法。
2.这个路由模板中的“action”占位符。
3.控制器中动作的参数。
在查找选择算法之前,我们需要理解控制器动作的一些事情。
控制器中的哪些方法被看成为是“动作”?当选择一个动作时,这个框架只考察控制器的public实例方法。而且,它会排除特殊名称的方法(构造器、事件、操作符、重载等等),以及集成自ApiController的类方法。
HTTP Methods
这个框架只会选择与请求的HTTP方法匹配的动作,确定如下:
1.你可以用注解属性AcceptVerbs、HttpDelete、HttpGet、HttpHead、HttpOptions、HttpPatch、HttpPost、或HttpPut来指定HTTP方法。
2.否则,如果控制器方法名称以“Get”、“Post”、“Put”、“Delete”、“Head”、“Options”、或“Patch”开头,那么根据这个约定,该Action将支持相应的HTTP方法。
3.如果以上都不是,那么这个方法将支持Post。
Parameter Bindings.
参数绑定是指Web API如何创建参数值。以下是参数绑定的默认规则:1.简单类型取自URI。2.复杂类型取自请求正文。
简单类型包括所有“.NET框架简单类型”,另外还有,DateTime、Decimal、Guid、String和TimeSpan。对于每一个动作,最多只有一个参数可以读取请求正文。
它也可以重写这种默认的绑定规则。See WebAPI Parameter binding under the hood。
在这种背景下,动作选择算法如下:
1.创建该控制器中与HTTP请求方法匹配的所有动作的列表。
2.如果路由字典有“action”条目,移除与该条目值不匹配的动作。
3.试图将动作参数与该URI匹配,如下:
a:针对每个动作,获得简单类型的参数列表,这是绑定得到URI参数的地方。该列表不包括可选参数。
b:从这个列表中,试着在路由字典或是在URI查询字符串中,找到每个参数的匹配。匹配是与大小写无关的,且与参数顺序无关。
c:选择这样的一个action,在列表中的每个参数在URI中有一个匹配。
d:如果满足这些条件的动作不止一个,选用参数匹配最多的一个。
4.忽略用[NonAction]注解属性标注的动作。
第3步可能会让人困扰。其基本思想是,可以从URI、或请求体、或一个自定义绑定来获取参数值。对于来自URI的参数,我们希望确保URI在其路径(通过路由字典)或查询字符串中实际包含了一个用于此参数的值。
例如,考虑以下动作:
public void Get(int id)
其id参数绑定到URI。因此,这个动作只能匹配在路由字典或查询字符串中包含了“id”值的URI。
可选参数是一个例外,因为它们是可选的。对于可选参数,如果绑定不能通过URI获取它的值,是没关系的。
复杂类型是另一种原因的例外。一个复杂类型只能通过自定义绑定来绑定到URI。但是在这种情况下,这个框架不能提前知道是否这个参数被绑定到一个特殊的URI。为了查明情况,这个框架需要调用这个绑定。选择算法的目的是在调用绑定之前根据静态描述来选择一个动作。因此,复杂类型是属于匹配算法之外的。
动作选择之后,会调用所有参数绑定。
Summary:
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 }
);


再看如下Contoller下的内容:


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请求:
http://localhost:34701/api/products/1?version=1.5&details=1
路由匹配:
该URI与名为“DefaultApi”路由匹配。路由字典包含以下条目:controller:"products",id:"1"。该路由字典并未包含查询字符串参数“version”和“details”,但这些将在动作选择期间考虑。
控制器选择:
根据路由字典中的“controller”条目,控制器类型是ProductsController。
动作选择:
这个HTTP请求是一个GET请求。支持Get的控制器动作是GetALL、GetById、FindProductsByName。这个路由字典不包含”action“条目,因此不需要匹配动作名称。
下一步,会试图匹配这些动作的参数名,只考查GET动作。
注意,不会考虑GetById的version参数,因为它是一个可选参数。
GetAll方法非常匹配。GetById方法也匹配,因为路由字典包含了“id”。FindProductsByName方法不匹配。
GetById方法是赢家,因为它匹配了一个参数,而GetAll无参数。该方法将以以下参数值被调用:id=1,version=1.5
注意,虽然version未被用于选择算法,但该参数值会取自URI查询字符串。
Extension Points
Web API为路由过程的某些部分提供了扩展点。
要为以上任一接口提供自己的实现,可使用HttpConfiguration对象的Services集合:
var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));
总结
Web API路由和动作选择的更多相关文章
- Asp.Net Web API 2第六课——Web API路由和动作选择
Asp.Net Web API 导航 Asp.Net Web API第一课——入门http://www.cnblogs.com/aehyok/p/3432158.html Asp.Net Web AP ...
- Web API路由与动作(三)
本章包括三个小节 如果你输入了mvc的路由规则 这个可以粗略过一遍即可 内容说明有点繁琐 原文地址:http://www.asp.net/web-api/overview/web-api-rout ...
- 【ASP.NET Web API教程】4.2 路由与动作选择
原文:[ASP.NET Web API教程]4.2 路由与动作选择 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本系列教程,请先看前面的内容. 4.2 Routing ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
- 【转载】Asp .Net Web Api路由路径问题
原文章地址:https://www.cnblogs.com/devtester/p/8897302.html MVC也好,WebAPI也好,据我所知,有部分人是因为复杂的路由,而不想去学的.曾经见过一 ...
- ASP.NET Web API路由解析
前言 本篇文章比较长,仔细思考阅读下来大约需要15分钟,涉及类图有可能在手机显示不完整,可以切换电脑版阅读. 做.Net有好几年时间了从ASP.NET WebForm到ASP.NET MVC再到ASP ...
- ASP.NET Web API 路由对象介绍
ASP.NET Web API 路由对象介绍 前言 在ASP.NET.ASP.NET MVC和ASP.NET Web API这些框架中都会发现有路由的身影,它们的原理都差不多,只不过在不同的环境下作了 ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- ASP.NET Web API 路由
路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...
随机推荐
- IOS应用沙盒文件操作
iOS沙盒机制 iOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等. 1 ...
- scanf 用法及陷阱(转)
函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设备( ...
- 数据库测试DbUnit
DBUnit 的设计理念就是在测试之前,备份数据库,然后给对象数据库植入我们需要的准备数据,最后,在测试完毕后,读入备份数据库,回溯到测试前的状态: 摘自:DbUnit入门实战 DBUnit官网:ht ...
- 在js中获取get参数(仿PHP)
复制粘贴即可..然后就可以在js中像PHP用$_GET['name']这样子获取get参数了!! /*--------------------(返回 $_GET 对象, 仿PHP模式)-------- ...
- Android 实用代码片段
一些不常见确又很实用的代码块. 1.精确获取屏幕尺寸(例如:3.5.4.0.5.0寸屏幕) public static double getScreenPhysicalSize(Activity ct ...
- cocos2d-x之Vector与map
bool HelloWorld::init() { if ( !Layer::init() ) { return false; } Size visibleSize = Director::getIn ...
- Linux traceroute
一.简介 traceroute 通过发送 TCP 数据包向目标端口进行探测,以检测源到目标服务器的整个链路上相应端口的连通性情况. 二.语法 -n 直接使用IP地址而非主机名称(禁用 DNS 反查 ...
- ubuntu 添加启动器
终于搞定了安卓开发环境,不知道折腾了多少次,多少个IDE,解决了一个问题,又冒出一个问题.烦死了,最后关头,都快放弃了,重启电脑,打开 android stuio 编译运行居然陈宫了,没有报错,why ...
- Lua环境
1.前言 Lua将其所有的全局变量保存在一个常规的table中,这个table称为“环境”.这种组织结构的优点在于,其一,不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实现:另一个优点 ...
- 读高性能JavaScript编程学英语 第一章第三页第一段话
When the browser encounters a <script> tag, as in this HTML page, there is no way of knowing w ...