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请 ... 
随机推荐
- IOS应用上传须要做的工作
			苹果开发人员 https://developer.apple.com/ 证书创建流程 certificates (证书): 是电脑可以增加开发人员计划的凭证 证书分为:开发证书和公布(产品)证书, ... 
- div显示和隐藏
			它是实现比较简单.style.display控制层隐藏或显示属性. <html> <body> <script> function show(){ document ... 
- Objective-c正确的写法单身
			Singleton模式iOS发展可能是其中最常用的模式中使用的.但是因为oc语言特性本身,想要写一个正确的Singleton模式是比较繁琐,iOS中单例模式的设计思路. 关于单例模式很多其它的介绍请參 ... 
- 几点思考-人生哲学,生活方式---ShinePans
			美结账时账单住酒店一晚800元.她抱怨太贵.经理说这是标准收费,带泳池的酒店.健身房和wifi. 美女说自己全然没使用,经理说饭店有提供.是她自己不用. 女客人打开皮包掏钱付账.但说要扣除经理和她共度 ... 
- 测试数据库sql声明效率
			书写sql当被发现的声明.对于所期望的结果通常是更好地执行. 当面对这些实现的时候如何选择它的最好的,相对来说?这导致了这个博客的话题,如何测试sql效率 以下介绍几种sql语句測试效率的方法,大多数 ... 
- 通过gradle运行测试脚本(转)
			练习一:HelloWorld 创建项目,源代码在src/main/java,测试源代码在src/test/java build.gradle的脚本: apply plugin: 'java' depe ... 
- html分析器——jericho-html-3.3分解table
			原部分来自Internet上的其他博客,只是因为很长一段时间.忘了谁是参考,这里说声抱歉.. 先贴一些html页: <html> <head> <meta http-eq ... 
- IT薪酬
			新加坡IT薪酬 2014-06-12 12:51 by 圣殿骑士, 8856 阅读, 37 评论, 收藏, 编辑 很多朋友发邮件或留言问我关于新加坡IT薪酬的问题,由于前段时间比较忙,所以没有及时一一 ... 
- 安装程序添加iis的方法经验分享
			原文:安装程序添加iis的方法经验分享 网上有一些这样的方法,但我这里主要做一些对比和扩充 网上这方面的文章的岁数比较大,server 08R2和win7出来后,整理这方面的资料的文章没找到,所以这里 ... 
- hdu4699 Editor 2013 多校训练第十场 D题 数列维护 splay | 线段树 | 栈!!!!!
			题意:维护一个文本编辑,并且查询最大前缀和. 写了splay,wa了13次 过了之后觉着特傻逼.发现题解两个栈就可以了,光标前后维护两个栈,维护前面的栈的前缀和 和 最大前缀和. 哎,傻逼,太弱了,还 ... 
