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请 ...
 
随机推荐
- 【十三】注入框架RoboGuice采用:(Logging via Ln)
			
上一篇我们简单的介绍了一下RoboGuice的使用([十二]注入框架RoboGuice使用:(Your First Injected ContentProvider)),今天我们来看下Log日志使用. ...
 - Redis是新兴的通用存储系统-为何Redis要比Memcached好用
			
GitHub版本地址: https://github.com/cncounter/translation/blob/master/tiemao_2014/Redis_beats_Memcached/R ...
 - c#里listview里如何获取点击的是哪一列
			
很多时候c#里会用到listview,又会给它添加很多的列,可以设置点击列的标头实现按照这列的内容排序等功能,然而点击不同列排序的方法是不一样的,这时候就需要知道,我到底是点击了哪一列,比如点击名字列 ...
 - 了解webpack
			
学习笔记 一步步了解webpack 前言 demo 地址: https://github.com/yy8597/webpack-demos 之前学习了 broswerify,发现确实很好用.虽然没 ...
 - [Python]新手写爬虫全过程(转)
			
今天早上起来,第一件事情就是理一理今天该做的事情,瞬间get到任务,写一个只用python字符串内建函数的爬虫,定义为v1.0,开发中的版本号定义为v0.x.数据存放?这个是一个练手的玩具,就写在tx ...
 - 用Ghostscript API将PDF格式转换为图像格式(C#)
			
原文:用Ghostscript API将PDF格式转换为图像格式(C#) 由于项目需要在.net下将pdf转换为普通图像格式,在网上搜了好久终于找到一个解决方案,于是采用拿来主义直接用.来源见代码中注 ...
 - Android.9图片评论(一个)
			
什么是.9图片 至于什么是.9图片这里就简单提一下,即图片后缀名前有.9的图片,如pic.9.png.pic1.9.jgp,诸如此类的图片就称为.9图片. .9图片的作用 ①.9图片的作用是在图片拉伸 ...
 - git - 简明指南(转)
			
安装 下载 git OSX 版 下载 git Windows 版 下载 git Linux 版 创建新仓库 创建新文件夹,打开,然后执行 git init 以创建新的 git 仓库. 检出仓库 执行 ...
 - 每天收获一点点------Hadoop概述
			
一.Hadoop来历 Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明了倒排索引算法,通过加入了Map ...
 - Python 目录操作(转)
			
在Python中,文件操作主要来自os模块,主要方法如下: os.listdir(dirname):列出dirname下的目录和文件os.getcwd():获得当前工作目录os.curdir:返回当前 ...