路由(Route)
Web Api中的路由与Asp.net mvc中的路由基本上一样,一个路由看起来像是一个URI路径,但是路由中包含一些大括号包括的占位符(place holder
),例如:
api/{Controller}/{Action}/{Id}
当你创建一个路由的时候,你可以为一个或多个占位符设置默认值,例如下面的例子将Controller
设为Account
,如果请求访问的URL没有提供Controller
时,将会使用设置的默认的Controller
defaults:new {Controller=Account}
你还可以为占位符设置一些限制,下面的例子限制Id只能为数字
constraints:new{ id = @"\d+"}
Web Api中的路由主要有三个主要的功能:
- 将请求的URI与路由模板进行匹配
- 选择Controller
- 选择Action
当收到请求是,Web Api会尝试着将请求URI与路由表(Route Table)中注册的路由模板进行比对,模板中的字面值(Literals
)必须被准确的匹配,如上面例子中的api片段,请求的URL必须包含api才能匹配上面的路由,而占位符可以匹配任何值(除非你有其它特殊的限制),将请求的URL进行匹配时,只会比对路由模板中有的片段(Segment),而路由模板中没有的则不会比对,如主机名(host name)、查询参数(query parameters)等,之后选择路由表中匹配的第一个路由来进行下一步的处理
Web Api提供的默认路由如下:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
default
中设置的id = RouteParameter.Optional
表示路由模板中id是不是必须的,而是可选的,只有在请求的URL存在时才会被赋值
路由字典(Route Dictionary)
如果请求的URL
匹配到了与之匹配的路由时,Web api会创建一个Dictionary
来存放URL
中与路由模板中占位符对应的字段值,这个Dictionary
的Key
就是路由模板的占位符的名称(不包括大括号),这些值即可能来自URL,也可能来自注册路由时设置的默认值。而这个Dictionary
就存放IHttpRouteData
类型的对象中。
注意 : 如果路由中的占位符被设置为 RouteParameter.Optional
,那么这个参数的值(即RouteParameter.Optional
)不会被加入到上述的Dictionary
中。但如果这个占位符被分配了具体的值的话,这个Dictionary
中还是会存储这个值的,例如:api/products
,那么此时这个Dictionary
中存储的就是:
- controller: “products”
- category:“all”
并不会存储:id:RouteParameter.Optional
,在比如,api/products/toys/123
,那么此时这个Dictionary
中存储的就是: - controller:“products”
- catagory: “toys”
- id : “123”
这个字典中甚至可以包含路由模板中不存在,但在默认值中存在的值,例如:
routes.MapHttpRoute(
name:"Root",
routeTemplate:"api/root/{id}",
default:new { controller="customers",id = RouteParameter.Optional}
)
如果URL中分离出的片段为api/root/8
,那么此时这个Dictionary
中存储的是:
- controller:"customers
- id : “8”
Controller
的选择
Controller
的选择是通过IHttpControllerSelector.SelectController
的方法来实现的,这个方法接收一个** HttpRequestMessage
类型对象返回一个 HttpControllerDescriptor
,默认使用的是Web api内部提供的 DefaultHttpControllerSelector
**,这个类使用下面的逻辑来选择Controller
:
以"Controller"为Key
到上述的Dictionary
(存储在IHttpRouteData
类型中)查找对应的Value
在获取到的值(即请求的Controller
名称)后面附加"Controller"字符串去获取对应的Controller
的类型名称
拿第二步得到的Controller
的名称去获取对应的Web Api Controller对象(ApiController
类型)
例如,如果路由字典(Route Dictionary)包含键值对(key-value pair)"Controller=products
",那么控制器的类型便是"ProductsController
",如果没有找到与之匹配的ApiController
类型或者有多个匹配,则返回一个错误给请求的客户端。
在第三步中,DefaultHttpControllerSelector
使用 IHttpControllerTypeResolver
(典型的IOC应用)接口去获取所有的ApiController
类型(均派生在ApiController),这个接口返回满足一下条件的所有公共(Public
)类型:
- 实现
IHttpController
接口 - 非抽象类
- 类名以"Controller"结束
Action的选择
在得到匹配的Controller
类型后,Web Api会调用IHttpActionSelector.SelectAction
方法去选择Action
,这个方法接收一个HttpControllerContext
类型参数,返回一个HttpActionSelector
类型实例。选择Action
的大致过程如下:
- 查看请求的Http Method(Get、POST等),获取请求的Action名称,方法和获取
Controller
名称的方法一致,以"Action"为Key到路由字典中获取对应值 - 查看路由表中注册的路由模板中的默认值
- 查看Controller中定义的Action方法的参数,找出参数最匹配的一个Action
那么,满足什么条件的方法才能被视为一个Action
呢?满足如下条件的方法便会被认为是Action
- 定义在
Controller
中的所有公共的实例方法(不包括一些具有特殊名称的方法,如构造函数、事件、运算符重载等)
Web Api仅仅选择那些与请求的Http Method匹配的Action
:
- 一个Action如果没有特殊说明,那么它的Http Method为POST
- 你可以通过
[HttpGet]、[HttpPost]、[HttpDelete]、[HttpHead]、[HttpOptions]、[HttpPatch]、[HttpPut]
特性来显式的声明一个Action
的支持的Http Method
,也可以通过HttpVerbs
特性来声明。 - 如果一个Action的名称以
Post、Get、Patch、Head、Put、Options、Delete
等字符开头的,则认为该Action
支持对应的Http
Method。
Action 的参数绑定
Web Api中,Action的参数的创建过程称为参数的绑定,遵照一下的规则:
- 简单类型(Simple Type)的参数从URI中获取其参数值
- 复杂类型(Complex Type)的参数从Http的请求报文中获取其参数值
那么如何区分简单类型和复杂类型呢?在Web Api中,如果一个数据类型支持源自字符串的类型转换,那么该数据对象就是简单类型,否则,该数据对象就是复杂类型,按照这个判断的标准,Net
中的所有的基元类型(Primative Type
)和可空值类型(Nullable Type
)、外加 DateTime, Decimal, Guid, String, and TimeSpan
.都是简单类型,而一个自定义的类型默认情况下都是复杂类型.对于一个Action
来说,之多有一个参数来自于Http
请求报文
注: 在C#中,能够直接被编译器识别的类型称为基元类型.如int、byte
等,其分别对应着FCL
中的System.Int32
和System.Byte
了解了上面的东西后,我们看一下Action
选择的机制(简单参数类型)
- 将 Controller中所有满足请求Http Method的Action放入一个列表中
- 如果路由字典中存在"Action"条目,则从创建好的Action列表中移除名称不匹配的Action.
- 尝试着通过URI去获取Action的参数
- 对于每个Action,获取其简单参数类型的参数列表,这其中不包括可选参数(Optional Parameters)
- 试着根据参数名称从参数列表、路由字典(
Route Dictionary
)或查询字符串(Query String
)中获取对应的参数值,这个匹配过程参数名称不区分大小写,也不依赖于参数的顺序。 - 选择一个
Action
,其参数列表中的每个参数在URI都有与之对应的参数值 - 如果有多个
Action
满足条件,则选择一个最为匹配的一个 - 忽略那些应用了[NonAction]特性的Action
其它
Web Api的路由过程是支持扩展的,当内置的实现逻辑不足以满足应用的需求时,此时,便可以通过实现相应的接口来对逻辑进行自定义。其中相关的接口及其作用如下:
接口 | 描述 |
---|---|
IHttpControllerSelector | 选择控制器 |
IHttpControllerResolver | 获取控制器类型列表,默认的DefaultHttpControllerSelector从 |
获取的控制器类型列表中选择控制器 | |
IAssemblyResolver | 获取项目的程序集列表,IHttpControllerTypeResolver使用这个列表去查找控制器类型 |
IHttpControllerActivator | 创建控制器实例 |
IHttpActionSelector | 选择Action |
IHttpActionInvoker | 执行Action |
在实现某个接口后,然后使用HttpConfiguration
类的Service
集合去注册后,Web Api便会使用自定义的逻辑去替换掉内置的默认实现。
var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector),new MyControllerSelector(config));