Web API-路由(二)
路由匹配主要有三个阶段:
1.将URI匹配到一个路由模版;
2.选择一个controller
3.选择一个action;
可以使用系统提供的拓展点,修改默认的匹配与选择逻辑规则。
路由模版:
路由模版很像一个URI但是它可以包含使用大括号包裹的占位符。
"api/{controller}/public/{category}/{id}"
我们可以定义占位符的默认值,defaults: new { category = "all" }
可以使用正则表达式定义约束条件:constraints: new { id = @"\d+" } // Only matches if "id" is one or more digits.
模版中的非占位符,文本部分必须严格匹配,占位符可以匹配任何值,除非你指定了约束条件。Web API 路由不会匹配URI的其他部分,比如主机名,和查询变量(一个uri : http://www.badiu.com/api/products/public/cat1/2?para1=a¶2=b,路由只关注红色的部分。框架会使用第一个匹配的模版(也就是说,路由模版记录是有顺序的)。
有两个特殊的占位符那就是{controller} 和 {action},很容易理解:
1.{controller}提供controller的名称;
2.{action}提供action的名称,在Web API中通常的惯例是省略"{action}";
暂时可以这样理解,除了{controller} 和 {action}两个占位符,其他的占位符一般用在匹配传递的参数(给uri中的参数命名以匹配action方法的参数,用作模型绑定用)。
路由字典(Route Dictionary)
当框架找到一个能够匹配URI的路由模版,它会创建一个字典类型对象来存储每一个占位符以及占位符匹配到的值,key值对应不包含大括号的占位符名称,vaule则为占位符在uri匹配到的值或者提供默认值。这个字典存储在一个IHttpRouteData类型的对象中。
在这个环节,特殊的占位符{controller}和{action}被当做普通的占位符对待,存储在字典中。
在给占位符指定默认值时,可以给它指定 RouteParameter.Optional值,如果给一个占位符指定了这个值,说明这个参数是可选的,如果不URI中没有指定这个参数,那么这个占位符以及他的值将不会添加到路由字典中去。比如:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{category}/{id}",
defaults: new { category = "all", id = RouteParameter.Optional }
);
如果uri为"api/products",那么路由字典包含:
controller:"products"
category:"all"
如果uri为"api/products/123",那么路由指定包含:
controller: "products"
category: "toys"
id: "123"
也就是说当指定一个占位符的默认值是:RouteParameter.Optional 时,uri没有为该占位符提供值时,字典中不包含该键值对,uri为该占位符提供值的时候,字典中包含该键值对。
还可以为没在路由模版中出现的占位符指定默认值:
routes.MapHttpRoute(
name: "Root",
routeTemplate: "api/root/{id}",
defaults: new { controller = "customers", id = RouteParameter.Optional }
);
这样匹配的controller总是为customers,路由字典包含:
controller: "customers"
id: "8"
控制器的选择(Selecting a Controller)
控制器的选择由 IHttpControllerSelector.SelectController 方法处理,这个方法接收一个HttpRequestMessage对象返回一个HttpControllerDescriptor对象。SelectController方法默认的实现由DefaultHttpControllerSelector类提供,这个类使用简单的控制器选择算法:
1.在路由字典中查找是否有键名为"controller"。
2.获取这个controller健对应的值,与“Controller”字符串组合成一个新字符串,通过这个字符串获得控制器类名称。
3.在Web API的控制器查找相同类型名称的控制器。
例如:路由字典中包含键值对"controller"="products",那么控制器类型即为"ProductsController"。如果有没有匹配的类型,获取有多个匹配的类型,框架返回一个错误给客户端。
在第三步中,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口去获取一个当前项目Web API控制器类型的列表。IHttpControllerTypeResolver默认的实现方法返回:
--所有实现了IHttpController的公开的(public)控制器。
--且.非抽象类型(are not abstract);
--且.命名以"Controller"结尾。
Action的选择(Action Selection)
在完成控制器的选择后,框架通过调用IHttpActionSelector.SelectAction方法来选择action,这个方法接收一个HttpControllerContext类型参数,返回一个HttpActionDescriptor类型对象,方法默认的实现由ApiControllerActionSelector类提供,选择Action的依据:
--HTTP请求方法(GET POST PUT ....)
--路由模版中是否有{action}占位符
--控制器中action方法的参数;
在了解action的选择算法之前,我们需要理解控制器 action的一些知识。
在控制器中什么样的方法可以作为action方法?
--控制器中的公共(public)方法,但不包括特殊名称(constructors, events, operator overloads, and so forth)的方法,和从ApiController中继承的方法。
HTTP请求方式:框架只选择能够匹配HTTP请求方式的action,由以下几个因素决定:
1.可以指定action方法可以匹配的HTTP请求方式,用 AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, 或者 HttpPut这些标识属性。
2.action方法名称以"Get", "Post", "Put", "Delete", "Head", "Options", 或者 "Patch" 开头。
3.如果action不满足上面两个条件,action方法默认处理POST请求。
参数绑定:Web API如何给action方法选择参数值,默认规则是:
--简单类型的参数值从URI中获取
--复杂类型的值从请求主体(request body)中获取;
简单类型包括所有.net 的基本类型( .NET Framework primitive types)以及 DateTime,Decimal,Guid,String和TimeSpan类型。对于一个action,最多只有一个参数能够读取请求主体(request body)???
修改默认的参数绑定规则,看WebAPI Parameter binding under the hood.
有了以上的知识,下面来看action的选择算法:
1.创建一个匹配当前请求方式的控制器中的action列表集合;
2.如果路由字典中有"action"键,再去除名称与字段中action键值不匹配的action;
3.尝试将action方法的参数与URI中传递的参数匹配:
--循环一个action,获取简单类型的参数列表(不包括可选类型参数)
--尝试将列表中的参数名与路由字典以及URI中查询参数进行匹配,这个匹配忽略大小写,以及参数顺序。
--选择一个:action方法的每一个参数都在路由字典或者URI查询参数中匹配到值的action。
--如果有多个action方法匹配成功,选择参数(不包括可选参数)最多的那个。
4.忽略带有[NonAction]标识属性的方法。
第三步,基本的意思是,一个action方法的参数能够从URI,请求主体(request body),或者自定义绑定(custom binding)获取它的参数值,对于来自URI中的参数,我们希望确定URI中是否包含一个当前action参数的值,无论是包含在路径(路由字典)中还是在查询参数中(query string)。(也就是这里的URI参数包括路由字典中的参数和查询字符串中的参数)
比如现在有一个action
public void Get(int id)
参数id需要绑定URI传递的信息,因此这个action只能匹配一个包括"id"值的URI(id值可以在路由路径中或者查询参数中)。
可选参数除外,如果URI中有为可选参数提供值则绑定,如果没有也没有影响,可选参数使用默认值。
复杂类型参数是一个例外(action选择时不会考虑复杂类型参数),复杂类型参数必须手工绑定,这个操作在action选择之后,所以在选择action时不会考虑复杂类型参数的匹配问题。
总结:
--action必须匹配HTTP请求方式
--如果指定了{action}占位符,action的名称必须与URI为占位符提供值相同;
--对于action的每一个参数,如果参数值从URI中获取,那么参数名称必须可以在路由字典或者URI查询条件(query string)中找得到(可选参数和复杂类型参数除外),这里有一个问题,请求主体(request body)中提供的键值对信息可不可以匹配action的参数???
--尝试匹配参数最多额action,最佳匹配方法可能没有参数。
综合实例:
路由模版定义如下:
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
这个URI匹配 "DefaultApi"路由模版,路由字典中包含:
controller:"products"
id:"1"
虽然路由字典不包含"version" 和 "details" 但在action选择时还是会考虑这两个参数。
控制器选择:ProductsController。
action选择:首先看Http请求方式 ,这里是GET方式,所以满足要求的只有GetAll ,GetById 和FindProductsByName 。再看路由模版中是否包含{action}占位符,这里不包含所以不用管action的名称,最后我们看满足要求的三个action的要求URI包括的参数,GetAll 没有参数,GetById 要求URI中包含id参数,FindProductsByName要求URI中包含name参数值,因为这里URI中不包含name参数值,所以排除FindProductsByName这个action,最后看匹配参数的个数,因为GetById匹配的参数个数1大于GetAll匹配参数个数0。所以最终选择GetById。(这里因为GetById的version参数是可选参数,选择action根本不会考虑这个参数,但绑定参数时会使用URI传递的参数值)。
一个问题可选参数是否影响action的选择,比如上面的例子中如果URI还包括一个name参数值。会选择哪个action 还是会报错??
结果:不影响。比如:同时包括id和name参数时报Multiple actions were found错误
拓展点:
Web API 为路由过程控制提供拓展点:
接口 |
描述 |
IHttpControllerSelector |
选择控制器 |
IHttpControllerTypeResolver |
获取控制器类型列表,DefaultHttpControllerSelector从获取的列表中选择控制器类型 |
IAssembliesResolver |
获取项目组件的列表,IHttpControllerTypeResolver接口使用这个列表去find控制器类型 |
IHttpControllerActivator |
创建一个新的控制器对象 |
IHttpActionSelector |
选择action |
IHttpActionInvoker |
调用action |
你可以在自定义的类中实现这些接口,然后使用HttpConfiguration对象的Services集合引用自定义的类来覆盖默认的实现类
var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));
Web API-路由(二)的更多相关文章
- Web API 路由 [二] Attribute Routing
1) 启用.在App_Start - WebApiConfig.cs下 //在Register函数添加如下代码: config.MapHttpAttributeRoutes(); 2) 使用.Cont ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- Web API路由与动作(三)
本章包括三个小节 如果你输入了mvc的路由规则 这个可以粗略过一遍即可 内容说明有点繁琐 原文地址:http://www.asp.net/web-api/overview/web-api-rout ...
- ASP.NET Web API 框架研究 ASP.NET Web API 路由
ASP.NET Web API 核心框架是一个独立的.抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到 ...
- ASP.NET Web API 路由对象介绍
ASP.NET Web API 路由对象介绍 前言 在ASP.NET.ASP.NET MVC和ASP.NET 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 ...
- ASP.NET Web API 路由
路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...
- Asp.Net Web API(二)
创建一个Web API项目 第一步,创建以下项目 当然,你也可以创建一个Web API项目,利用 Web API模板,Web API模板使用 ASP.Net MVC提供API的帮助页. 添加Model ...
- Asp.Net Web APi 路由的特点
在ASP.NET Web API中,路由是基于HTTP协议 GET请求路由到以GET开头的控制器方法,POST请求路由到以POST开头的控制器方法中,GET方法和GetProducts,都能与GET请 ...
随机推荐
- nginx 日志和监控
原文地址:http://nginx.com/resources/admin-guide/logging-and-monitoring/ Logging and Monitoring 日志和监控 Thi ...
- UBuntu经常使用的操作(网络资源)
http://docs.google.com/Doc? id=dqsbw4c_46d89djccr 版权声明:本文博主原创文章.博客,未经同意不得转载.
- 阿里云+wordpress
阿里云+wordpress搭建个人博客网站[小白专用的图文教程] [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源 ...
- FastReport扩展类
题记: 最近有在用FastReport进行开发报表工作,当然也有在看书,突然想到可以用书中所写来实现一个fastreport的帮助类. 对于引用第三方类库,我们都会去将这个库在调用前进行相应的封装,也 ...
- 关于苹果公司最新的语言Swift
Swift供IOS和OSX新的编程语言开发的应用程序,吸取C和Objective-C质朴的语言.但没有损失C兼容性语言.Swift使用安全的编程模型.增加各种现代编程语言功能,使语言更容易掌握.更具可 ...
- Ubuntu14.04设备JDK
1.设备JDK 打开命令直插式工具.输入以下三个命令: sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo ap ...
- C++四种类型的转换
在C/C++使用的语言 (type) value(您还可以使用type(value))对于显式类型转换,经常提到投.转换程序猿的精度等完全掌握手,一个传统投往往是过度使用.成为C++要根源. 为了降低 ...
- 第一次QQ群视频教育有感
标题:第一次QQ群视频教育有感 作者:丁又专, 时间:2014.08.16 教育的目的:启示学生心智,发现个人优势,激发探索欲望. 今天早上看到 中国大学MOOC<文献管理与信息 ...
- cocos2dx 使得单麻将(三)
cocos2dx 使得单麻将(三) 麻将逻辑4.得到手牌数据 我们已经保存了一个一维数组, 类似于一个表格,统计出全部牌相应的数量, 但我们如何得到当前手中是什么牌呢 //扑克转换 BYTE Swit ...
- js 性能优化整理之 高频优化
mousemove 拖拽操作 var count = 0; elem.onmousemove = function(){ count++; // 当计数器为偶数的时候不执行mousemove if( ...