Web Api 2(三)之路由与Action的选择
路由(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));