再《上篇》中我们简单介绍了用于实现Action选择机制的HttpActionSelector,接下来我们来讨论本章最为核心的内 容:ASP.NET Web API如何利用HttpActionSelector(以默认的使用的ApiControllerActionSelector为例)在目标 HttpController成功激活之后如何从中选择出匹配的Action方法来处理当前的请求。[本文已经同步到《How ASP.NET Web API Works?》]

目录
候选的Action列表
针对HttpRouteData中的Action名称进行筛选
针对支持的HTTP方法进行筛选
针对查询字符串和路由变量进行筛选
针对ActionMethodSelector进行筛选
异常处理

候选的Action列表


了便于理解,我们通过一个具体的例子来实现在ApiControllerActionSelector的SelectAction方法中的Action选
择策略。我们假设在一个ASP.NET Web API应用中定义了如上一个继承自ApiController的DemoController。

  1. 1: public class DemoController : ApiController
  1. 2: {
  1. 3: //无参数
  1. 5: public string Get()
  1. 6: {
  1. 7: return "DemoController.Get()";
  1. 8: }
  1. 4: [NonAction]
  1. 9: [HttpGet]
  1. 10: [ActionName("Get")]
  1. 11: public string Retrieve()
  1. 12: {
  1. 13: return "DemoController.Retrieve()";
  1. 14: }
  1. 15: 
  1. 16: //一个参数
  1. 17: public string Get(string x)
  1. 18: {
  1. 19: return "DemoController.Get(string x)";
  1. 20: }
  1. 21: 
  1. 22: //两个参数
  1. 23: public string Get(string x, string y)
  1. 24: {
  1. 25: return "DemoController.Get(string x, string y)";
  1. 26: }
  1. 27: public string Get(int x, int y)
  1. 28: {
  1. 29: return "DemoController.Get(int x, int y)";
  1. 30: }
  1. 31: 
  1. 32: //Put, Post & Delete
  1. 33: public string Put()
  1. 34: {
  1. 35: return "DemoController.Put()";
  1. 36: }
  1. 37: public string Post()
  1. 38: {
  1. 39: return "DemoController.Post()";
  1. 40: }
  1. 41: public string Delete()
  1. 42: {
  1. 43: return "DemoController.Delete()";
  1. 44: }
  1. 45: }

DemoController 一共定义了8个Action方法,它们均返回表示各自方法签名的字符串。前面两个方法Get和Retrieve不具有参数,Retrieve方法上应用了 3个特性:HttpGetAttribute特性使之支持HTTP-GET请求,ActionNameAttribute将Action名称设置为 “Get”,NoActionAttribute特性使它不能通过请求直接调用。第3个Get方法具有一个字符串类型的参数x。第4和第5个Get方法均 具有两个同名的参数x和y。除了这5个基于HTTP-GET请求的Action方法之外,我们还针对HTTP-PUT、HTTP-POST和HTTP- DELETE定义了3个对应的Action方法。我们将这8个Action方法和自己支持的HTTP方法列在如下所示的表格中。

假 设我们直接采用创建ASP.NET Web API项目默认提供的路由注册,相关的代码如下所示。接下来我们采用不同的URL对这个DemoController进行访问,并分析 ApiControllerActionSelector针对每一个具体的请求是如何将目标Action方法筛选出来的。

  1. 1: public static class WebApiConfig
  1. 2: {
  1. 3: public static void Register(HttpConfiguration config)
  1. 4: {
  1. 5: config.Routes.MapHttpRoute(
  1. 6: name : "DefaultApi",
  1. 7: routeTemplate : "api/{controller}/{id}",
  1. 8: defaults : new { id = RouteParameter.Optional }
  1. 9: );
  1. 10: }
  1. 11: }

针对HttpRouteData中的Action名称进行筛选

我 们知道HttpActionSelector具有两个基本方法,GetActionMapping返回一个描述定义在目标HttpController中 所有Action方法的HttpActionDescriptor与Action名称的映射。具体来说,该方法返回的是一个 ILookup<string, HttpActionDescriptor>对象,它具有如上表所示的结构。SelectAction方法将这个映射表作为最初的候选 HttpActionDescriptor列表,并通过多轮筛选最终得到用于处理当前请求的那一个。

ApiControllerActionSelector 针对请求提供的Action名称进行第一轮筛选。具体来说,它先从指定的HttpControllerContext中提取用于封装路由数据的 HttpRouteData对象,如果其中包含目标Action的名称(对应条目的Key为“action”),它会从候选项中筛选出Action名称与 之匹配的HttpActionDescriptor列表。反之,如果HttpRouteData中并不包含目标Action的名称,此项筛选工作将会忽 略。

对于我们给出的这个例子来说,由于注册路由时并不曾在URL模板中定义针对Action名称的变量,所以生成的HttpRouteData中是不会包含目标Action名称的。

  1. 1: public static class WebApiConfig
  1. 2: {
  1. 3: public static void Register(HttpConfiguration config)
  1. 4: {
  1. 5: 
  1. 6: config.Routes.MapHttpRoute(
  1. 7: name : "DefaultApi",
  1. 8: routeTemplate : "api/{controller}/{action}/{id}",
  1. 9: defaults : new { id = RouteParameter.Optional }
  1. 10: );
  1. 11: }
  1. 12: }

如 果我们按照如上的方式让注册路由的URL模板包含一个针对目标Action名称的变量“{action}”,针对一个URL为“/api /contacts/get”的请求,ASP.NET Web API 的路由系统生成的HttpRouteData将会包含目标Action的名称“get”,那么如右图所示的5个同名的Action会率先被筛选出来。

针对支持的HTTP方法进行筛选

ApiControllerActionSelector 进行的第2轮筛选工作是针对候选Action所支持的HTTP方法。具体来说,它通过候选HttpActionDescriptor对象的 SupportedHttpMethods属性得到对应Action支持的HTTP方法列表,如果此列表包含当前请求的HTTP方法,那么此 HttpActionDescriptor会被筛选出来。

对于定义在DemoController中的8个Action方法来说,如果请求 采用的HTTP方法为HTTP-GET,那么如右图所示的5个唯一支持HTTP-GET请求的Action会被筛选出来;如果当前接收的是分别是一个 HTTP-PUT、HTTP-POST或者HTTP-DELETE请求,那么与之对应的Action方法Put、Post和Delete会被筛选出来。

针对查询字符串和路由变量进行筛选

绑 定到目标Action方法上的部分参数(参数类型支持源自字符串的类型转换)值来源于请求的URL,具体来说可以源自URL的路径,也可以源自URL的查 询字符串。对于前者,其值一般会转换成路由变量被添加到路由系统生成的HttpRouteData中。第三轮筛选工作就是针对请求URL的查询字符串进行 的。

一个Action方法能够正常执行,其前提是方法的参数值能够正常绑定。对于调用者来说,它发送的请求中必须提供执行目标方法所需的参 数值。针对众多候选Action来说,如果其参数值应该由请求URL的查询字符串或者生成HttpRouteData来提供,能够被选择用于处理某个请求 的Action必须满足这样的条件:当前请求URL的查询字符串和生成的HttpRouteData能够提供这种类型的所有参数。

以我们定 义的DemoController为例,如果接收到的是一个URL为“/api/demo?x=1”的HTTP-GET请求,对于5个支持HTTP- GET的Action来说,只有前面3个Action方法(Get()、Retrieve()和Get(string x))的参数能够通过URL的查询字符串来提供,所以会被选择。对于其余两个Action方法(Get(string x, string y)、Get(int x, int y))来说,它们的参数y无法从请求中获得,所以会被排除在选择范围之外。

我们现在来讨论针对查询字 符串和路由变量的筛选在ApiControllerActionSelector中具体是如何实现的。对于每个候选的 Action,ApiControllerActionSelector都会通过一个字符串数组保存一组参数名,对应参数绑定的值来源于请求的URL。具 体来说,这些参数必须满足如下三个条件:

  • 参数不应该是一个缺省参数,因为缺省参数具有默认值,对应值不一定要存在于请求的URL中。
  • 参数类型必须支持源自字符串的类型转换,因为参数值是以字符串的形式出现在URL中的。
  • 参数对应HttpParameterBinding的从当前URI中读取数据。

针对上轮筛选得到的5个Action来说,按照上述的这三个条件,这个用于保存“必须通过URL提供的参数值”的参数名称数组如左图所示。为了便于后续说明,我们将这个数组命名为UrlParameters。

在 开始本轮筛选时,ApiControllerActionSelector会通过生成HttpRouteData得到所有的路由变量名,然后将表示目标 HttpController名称的变量名“controller”和表示目标Action名称的变量名“action”(如果有)剔除出去,我们将得到 字符串集合命名为ParameterNames1。然后它会从请求URL中提取所有的查询字符串名称,假设该字符串集合命名为 ParameterNames2。最后ParameterNames1和ParameterNames2进行合并(合并时针对字符串的比较不区分大小写) 得到一个新的字符串集合ParameterNames3。

本着“请求必须提供目标Action方法所需参数值”的原则,对于候选的众多Action而言,只有它们的UrlParameters是ParameterNames3的子集才会被选择。但是是否所有满足这个条件的Action都能成为 本轮筛选后剩余的成员呢?

就 我们给出的例子来说,如果当前请求(HTTP-GET)的URL为“/api/demo?x=1”,那么前面3个Action满足上面的条件,是否意味着 这三个Action均会来选择呢?如果请求URL变成“/api/demo?x=1&y=2”,无疑5个Action都满足上述的条件,那么本轮 筛选会选择所有这5个Action吗?

就我们的经验来说,当我们调用第3个Action(Get(string x))才会采用“/api/demo?x=1”这样的URL;如果请求URL为“/api/demo?x=1&y=2”,一般访问的是最后两个 Action(Get(string x, string y)、Get(int x, int y))。实际上ApiControllerActionSelector最终也是这样选择:对于众多符合条件候选Action来说,它只会保留UrlParameters元素最多哪一组Action。

上 面介绍的都是请求URL具有查询字符串或者生成的HttpRouteData包含相应路由变量(不包括表示目标HttpController和 Action名称的路由变量)情况下针对Action的筛选。如果URL根本不包含任何查询字符串,而且生成的HttpRouteData也不包含任何的 路由变量,此时的Action筛选工作就变得简单:只会选择UrlParameters为空的Action。

综 合上面两种情况下Action筛选策略,针对具有不同URL(“/api/demo”、“/api/demo?x=1”、“/api/demo?x=1& amp;y=2”)的HTTP-GET请求,上轮筛选后剩余的5个候选Action在本轮筛选之后保留的Action列表如右图所示。

针对ActionMethodSelector进行筛选

通 过前面的介绍我们知道ActionMethodSelector用于判断针对具体的请求目标Action是否是有效的,最后一轮筛选就是针对应用到 Action方法上的ActionMethodSelector进行的,具体的筛选逻辑很简单:直接获取应用到候选Action方法上实现了接口 IActionMethodSelector的特性,将当前HttpControllerContext和Action方法对应的MethodInfo作 为参数调用其IsValidForRequest方法,只有返回值为True的Action才会被保留。

我 们也知道IActionMethodSelector是一个内部接口类型,目前只有一个唯一的NoActionAttribute实现了这个接口,所以目 前我们只需要关注NoActionAttribute就可以了。本轮筛选会将应用了NoActionAttribute特性的Action剔除掉。

对 于我们给出的这个例子来说,只有Retrieve方法上应用了NoActionAttribute特性,所以如果它包含在候选的Action列表中,它将 被剔除出去。同样是针对URL分别为“/api/demo”、“/api/demo?x=1”、“/api/demo?x=1&y=2”的三种 HTTP-GET请求,最终保留下来的Action列表如图5-9所示。

异常处理

ASP.NET Web API针对请求选择Action的默认策略,即定义在ApiControllerActionSelector的SelectAction方法的实现逻辑 一共会经历4轮筛选。如果最终选择的结果仅仅具有一个唯一的Action,那么它将会用于处理当前请求。此处之外,还具有如下两种异常情况:

  • 没有任何一个Action被选择
  • 具有多个匹配的Action

对 于前者,ASP.NET Web API直接返回一个状态为“404, Not Found”的响应。对于后者,会因为不能决定哪个才是用于处理当前请求的Action而抛出一个InvalidOperationException异 常。对于我们给出的例子来说,如果接收的HTTP-GET请求的URL分别为“/api/demo”、“/api/demo?x=1”,由于只有唯一的 Action被选择,所以请求能够被正常处理。如右图所示,我们直接通过浏览器访问者两个地址,会直接得到我们希望的结果。

如 果请求URL为“/api/demo?x=1&y=2”,我们知道这种情况下会有两个Action方法与请求匹配,所以这样的请求得不到正常处 理。如左图所示,当我们访问这个地址的时候,浏览器直接将包含错误信息的XML呈现出来。具体的错误信息为“Multiple actions were found that match the request: System.String Get(System.String, System.String) on type WebApi.Controllers.DemoController System.String Get(Int32, Int32) on type WebApi.Controllers.DemoController”,即“在目标HttpController中定义了多个与当前请求匹配的 Action”。

现在我们对DemoController作如下的修改,直接将NoActionAttribute特性应用到无参数的无参的Get方法上。

  1. 1: public class DemoController : ApiController
  1. 2: {
  1. 3: //其他成员
  1. 6: [NoAction]
  1. 7: public string Get()
  1. 8: {
  1. 9: return "DemoController.Get()";
  1. 10: }
  1. 11: }

按 照上面介绍的Action选择策略,如果当前是一个URL为“/api/demo”的HTTP-GET请求,在目标HttpController中将找不 到任何匹配的Action,此时客户端将接收到状态为“404,Not Found”的响应。同样通过浏览器访问这个地址,我们会得到如右图所示的结果。

ASP.NET Web API是如何根据请求选择Action的?[下篇] 【转】的更多相关文章

  1. ASP.NET Web API是如何根据请求选择Action的?[下篇]

    ASP.NET Web API是如何根据请求选择Action的?[下篇] 再<上篇>中我们简单介绍了用于实现Action选择机制的HttpActionSelector,接下来我们来讨论本章 ...

  2. ASP.NET Web API是如何根据请求选择Action的?[上篇]

    ASP.NET Web API是如何根据请求选择Action的?[上篇] Web API的调用请求总是针对定义在某个HttpController中的某个Action方法,请求响应的内容来源于调用目标A ...

  3. ASP.NET Web API是如何根据请求选择Action的?[上篇] 【转】

    http://www.cnblogs.com/leo_wl/p/3316548.html ASP.NET Web API是如何根据请求选择Action的?[上篇] Web API的调用请求总是针对定义 ...

  4. Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?

    构成ASP.NET Web API核心框架的消息处理管道既不关心请求消息来源于何处,也不需要考虑响应消息归于何方.当我们采用Web Host模式将一个ASP.NET应用作为目标Web API的宿主时, ...

  5. 【转】WCF和ASP.NET Web API在应用上的选择

    文章出处:http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html 在最近发布的Visual Studio 2012及.NET 4. ...

  6. WCF和ASP.NET Web API在应用上的选择

    小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/shareto ...

  7. [转载]WCF和ASP.NET Web API在应用上的选择

    http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html http://msdn.microsoft.com/en-us/libra ...

  8. WCF和ASP.NET Web API在应用上的选择(转)

    出处:http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html 在最近发布的Visual Studio 2012及.NET 4.5中 ...

  9. ASP.NET Web API 2.0新特性:Attribute Routing1

    ASP.NET Web API 2.0新特性:Attribute Routing[上篇] 对于一个针对ASP.NET Web API的调用请求来说,请求的URL和对应的HTTP方法的组合最终决定了目标 ...

随机推荐

  1. springmvc和struts2的区别比较

    1.Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上Spr ...

  2. Linux编程之变量

    Bash变量与变量分类 变量命名规则 变量名必须以字母或下划线打头,名字中间只能由字母.数字和下划线组成 变量名的长度不得超过255个字符 变量名在有效的范围内必须是唯一的 在Bash中,变量的默认类 ...

  3. 用Excel的分列功能格式化时间

    从数据库导出的时间是带有毫秒的,怎么变成短的时间呢? 首先在时间列后面插入一列: 第二,点击数据菜单,点击分列,下一步,然后输入分割符号,然后就变成了两列.

  4. 【转】Unity3D学习日记(一)使用UGUI制作虚拟摇杆

    http://blog.csdn.net/begonia__z/article/details/51170059 如今手机游戏玩法多种多样,尤其使用虚拟摇杆进行格斗类游戏开发或者是MMORPG成为了主 ...

  5. html5中checkbox的选中状态的设置与获取

    获取checkbox是否选中: $("#checkbox").is(":checked"); 获得的值为true或false. 设置checkbox是否选中: ...

  6. Struts2 改变语言状态

    只要在请求中增加 request_locale=en_US 参数,就可以实现语言的切换,内部由拦截器实现

  7. 【bzoj2529】[Poi2011]Sticks 贪心

    题目描述 给出若干木棍,每根木棍有特定的颜色和长度.问能否找到三条颜色不同的木棍构成一个三角形.(注意这里所说的三角形面积要严格大于0) 输入 第一行给出一个整数k(3<=k<=50),表 ...

  8. [HNOI2004][bzoj1212] L语言 [Trie+dp]

    题面 传送门 思路 无后效性 显然,不管某个前缀的理解方式是怎么样的,如果它能被理解,那么前面的决策对于后面的决策而言都是等价的 因此这题可以DP DP方程 令$dp[i]$表示前缀i是否能被理解 那 ...

  9. filesystem

    1 tmpfs 以下来源于维基百科: tmpfs是类Unix系统上暂存档存储空间的常见名称,通常以挂载文件系统方式实现,并将数据存储在易失性存储器而非永久存储设备中.和RAM disk的概念近似,但后 ...

  10. tableView镶嵌加入CollectionView实现方法

    创建一个继承UICollectionView的类QHCollectionView在QHCollectionView.h中添加接口方法 @interface QHCollectionView : UIC ...