ASP.NET Web API 框架研究 Action的选择
如何从HttpController众多方法里如何选择出有效的Action方法?主要分一下几个步骤:
首先,获取候选HttpActionDescriptor列表(ILookup(string,HttpActionDescriptor>类型)
通过作为参数的HttpControlIerDescriptor对象获取当前HttpControlIer的真实类型,然后调用其GetMethods方法获得描述所有方法成员的MethodInfo对象列表,并从中筛选出所有“有效”的Action方法来创建相应ReflectedHttpActionDescriptor对象。有效的Action方法要满足以下几个条件:
- 公有实例方法
- 不是从ApiController类型中继承的方法
- MethodInfo的IsSpecialName属性值为False(表示属性成员Geter和Seter的MethodInfo不会被用于创建HttpActionDescriptor)
- 没有定义NonActionAttribute
我们知道,ActionNameAttribute特性让多个Action方法共享同—个名称,是一个—对多的关系,所以,我们过滤出有效Action方法后,构建一个ILookup(string,HttpActionDescriptor>类型的对象作为候选HttpActionDescriptor列表,Key为Action名称,Value为HttpActionDescriptor。
其次,根据请求解析出的路由提供的Action名称进行筛选
从指定的HttpControllerContext对象中提取用于封装路由数据的HttpRouteData对象,若包含Action的名称,就从待选HttpActionDescriptor列表(ILookup(string,HttpActionDescriptor>类型),获取Action名称与它匹配的HttpActionDescriptor列表,否则,忽略该步骤。
再次,根据请求的Http方法与待选列表种的每个HttpActionDescriptor所支持的HttpMethod匹配
通过候选HttpActionDescriptor对象的SupportedHttpMethods属性得到对应Action方法支持的HTTP方法列表,如果此列表包含当前请求的HTTP方法,那么此HttpActionDescriptor会被筛选出来。
从次,参数的匹配
参数的来源有两种:
- URL的路径一部分,会转换成路由变量,如api/product/{id}
- URL查询字符串,如api/product?id=1
针对众多候选Aotion方法来说,如果它们的参数值应该由请求URL的查询字符串或者生成的HttpRouteData对象来提供,能够被选择用于处理某个请求的Action必须满足这样的条件和原则:
- 当前请求URL的查询字符串和生成的HttpRouteData能够提供这种类型的所有参数
- 如果参数匹配有多个方法满足,选择Action方法参数多的
参数匹配有点模糊,举个例子,控制器中有以下几个Action方法,
//无参数
public string Get()
{
return "DemoController.Get()";
}
[HttpGet]
[ActionName("Get")]
public string Retrieve()
{
return "DemoController Retrieve";
}
//一个参数
public string Get(string x)
{
return "DemoController.Get(string x)"
}
//两个参数
public string Get(string x, string y)
{
return "DemoController.Get(string x, string y)"
}
public string Get(int x, int y)
{
return "DemoController.Get(int x, int y)"
}
如果接收到的是一个URL为“/api/demo?x=1” 的GET请求,对于5个GET的Action来说,只有前面3个ActIon方法(Get、Retrieve和Get(string x)的参数能够通过URL的查询字符串来提供,所以它们会被选择。Get和Retrieve不需要参数,URL中提供了一个,所以也选中。
对于其余两个Action方法,它们的参数y无法从请求中获得,所以会被排除在选择范围之外。
具体实现,会为每个Action方法建立必须通过URL提供的参数值的参数名称数组,以上五个方法,对应的数组如下:
[],[],["x"],["x","y"],["x","y"]
再根据请求URL从两种参数来源,获取不重复的所有参数值(不区分大小写),如/api/demo?x=1&y=2,URL提供的参数是["x","y"]
根据“请求必须提供执行目标Action方法所需参数值”条件,以上5个方法都匹配,又根据“有多个符合条件情况下,选择Action方法参数多的”原则,只有最后两个Action方法会选中(Get(string x, string y)、Get(int x, int y))。
最后,匹配结果的异常处理
经过前边四步匹配得到的结果及处理情况如下:
- 一个Action方法 ------ 直接执行
- 具有多个符合筛选条件的Action方法 ------- 抛出异常InvalidoperationException的异常
- 不具有符合筛选条件的Action方法,但在不考虑请求采用的HTTP方法的情况下,具有—个或者多个Action 方法与请求相匹配 -------- 回复—个状态为“405 ,Method Not Allowed” 的响应,并将候选Action方法支持的HTTP方法列表置于名为“Accept”的响应报头中
- 不论是否考虑请求采用的HTTP方法,都不具有符合筛选条件的Action方法 ----------- 直接回复一个状态为“404,Not Found” 的响应
一、涉及的类及源码分析
1、IHttpActionSelector ApiControllerActionSelector
Action的选择通过标准组件HttpActionSelector完成,提供了一个接口IHttpActionSelector,以及一个默认实现ApiControllerActionSelector,其次也是在服务容器里指定的
接口如下:
public interface IHttpActionSelector
{
ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
}
ApiControllerActionSelector基本代码如下:

回顾下抽象类ApiController里的ExecuteAsync方法的代码:
//主要方法,创建控制器对象后,会调用ExecuteAsync方法,进行后续操作,
public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
if (_initialized)
{
// 如果已经创建过该实例,就抛出异常,一个控制器实例,多次请求不能重复使用
throw Error.InvalidOperation(SRResources.CannotSupportSingletonInstance, typeof(ApiController).Name, typeof(IHttpControllerActivator).Name);
} Initialize(controllerContext);
if (Request != null)
{
//先注册到待销毁集合,待请求完成后一起销毁改控制器实例
Request.RegisterForDispose(this);
}
HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
//选择Action,通过该语句来触发控制器选择,返回ActionDescriptor
HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
ActionContext.ActionDescriptor = actionDescriptor;
if (Request != null)
{
Request.SetActionDescriptor(actionDescriptor);
} FilterGrouping filterGrouping = actionDescriptor.GetFilterGrouping(); //ActionFilters
IActionFilter[] actionFilters = filterGrouping.ActionFilters;
//身份认证过滤器
IAuthenticationFilter[] authenticationFilters = filterGrouping.AuthenticationFilters;
//授权过滤器
IAuthorizationFilter[] authorizationFilters = filterGrouping.AuthorizationFilters;
//ExceptionFilters
IExceptionFilter[] exceptionFilters = filterGrouping.ExceptionFilters; IHttpActionResult result = new ActionFilterResult(actionDescriptor.ActionBinding, ActionContext,
controllerServices, actionFilters);
if (authorizationFilters.Length > )
{
result = new AuthorizationFilterResult(ActionContext, authorizationFilters, result);
}
if (authenticationFilters.Length > )
{
result = new AuthenticationFilterResult(ActionContext, this, authenticationFilters, result);
}
if (exceptionFilters.Length > )
{
IExceptionLogger exceptionLogger = ExceptionServices.GetLogger(controllerServices);
IExceptionHandler exceptionHandler = ExceptionServices.GetHandler(controllerServices);
result = new ExceptionFilterResult(ActionContext, exceptionFilters, exceptionLogger, exceptionHandler,
result);
}
//执行IHttpActionResult的ExecuteAsync
return result.ExecuteAsync(cancellationToken);
}
可以知道主要是在该方法中调用了ApiControllerActionSelector类的SelectAction方法来返回HttpActionDescriptor

我们主要看下SelectAction源代码:
//接口IHttpActionSelector方法
public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
} ActionSelectorCacheItem internalSelector = GetInternalSelector(controllerContext.ControllerDescriptor);
return internalSelector.SelectAction(controllerContext);
} ///接口IHttpActionSelector方法
public virtual ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
{
if (controllerDescriptor == null)
{
throw Error.ArgumentNull("controllerDescriptor");
}
//从internalSelector中获取ILookup<string, HttpActionDescriptor>
ActionSelectorCacheItem internalSelector = GetInternalSelector(controllerDescriptor);
return internalSelector.GetActionMapping();
}
其中的GetInternalSelector方法返回一个ActionSelectorCacheItem,再调用其的SelectAction方法
GetInternalSelector方法代码如下:
private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor)
{
// 性能敏感的 // 先从本地快速缓存查找,如果没有再从HttpControllerDescriptor的字典属性缓存中找
if (_fastCache == null)
{
ActionSelectorCacheItem selector = new ActionSelectorCacheItem(controllerDescriptor);
//线程安全,比较_fastCache是否为null,如果是,用selector替换_fastCache,不为null,说明_fastCache已经被修改了,不替换
Interlocked.CompareExchange(ref _fastCache, selector, null);
return selector;
}
else if (_fastCache.HttpControllerDescriptor == controllerDescriptor)
{
// If the key matches and we already have the delegate for creating an instance then just execute it
return _fastCache;
}
else
{
// 如果键不匹配,则在HttpControllerDescriptor属性中查找
object cacheValue;
if (controllerDescriptor.Properties.TryGetValue(_cacheKey, out cacheValue))
{
return (ActionSelectorCacheItem)cacheValue;
}
//如果找不到,就创建一个ActionSelectorCacheItem,并放到HttpControllerDescriptor属性中
ActionSelectorCacheItem selector = new ActionSelectorCacheItem(controllerDescriptor);
controllerDescriptor.Properties.TryAdd(_cacheKey, selector);
return selector;
}
}
第一次访问时候,我们会创建一个ActionSelectorCacheItem,构造函数如下:
public ActionSelectorCacheItem(HttpControllerDescriptor controllerDescriptor)
{
Contract.Assert(controllerDescriptor != null); // Initialize the cache entirely in the ctor on a single thread.
_controllerDescriptor = controllerDescriptor;
//一、首先,获取候选HttpActionDescriptor列表
//通过反射查找出所有公有的实例方法
MethodInfo[] allMethods = _controllerDescriptor.ControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
//通过委托IsValidActionMethod过滤出有效的方法(非指定名称的方法,不是从IHttpController, ApiController继承的方法,不定义NonActionAttribute)
MethodInfo[] validMethods = Array.FindAll(allMethods, IsValidActionMethod); _combinedCandidateActions = new CandidateAction[validMethods.Length];
for (int i = ; i < validMethods.Length; i++)
{
//根据MthodInfo创建ReflectedHttpActionDescriptor对象
MethodInfo method = validMethods[i];
ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(_controllerDescriptor, method);
_combinedCandidateActions[i] = new CandidateAction
{
ActionDescriptor = actionDescriptor
};
HttpActionBinding actionBinding = actionDescriptor.ActionBinding; // Building an action parameter name mapping to compare against the URI parameters coming from the request. Here we only take into account required parameters that are simple types and come from URI.
_actionParameterNames.Add(
actionDescriptor,
actionBinding.ParameterBindings
.Where(binding => !binding.Descriptor.IsOptional && TypeHelper.CanConvertFromString(binding.Descriptor.ParameterType) && binding.WillReadUri())
.Select(binding => binding.Descriptor.Prefix ?? binding.Descriptor.ParameterName).ToArray());
}
//
_combinedActionNameMapping =
_combinedCandidateActions
.Select(c => c.ActionDescriptor)
.ToLookup(actionDesc => actionDesc.ActionName, StringComparer.OrdinalIgnoreCase);
}
这里会获取待选的Action列表,IsValidActionMethod委托代码如下:
private static bool IsValidActionMethod(MethodInfo methodInfo)
{
//非指定名称的方法,即不能被外部用户直接调用的方法,通过其他方式间接调用,如构造函数
if (methodInfo.IsSpecialName)
{
return false;
}
//不是从IHttpController, ApiController继承的方法
if (methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(TypeHelper.ApiControllerType))
{
return false;
}
//不定义NonActionAttribute
if (methodInfo.GetCustomAttribute<NonActionAttribute>() != null)
{
return false;
} return true;
}
创建完ActionSelectorCacheItem后,返回ApiControllerActionSelector的SelectAction方法,调用创建的ActionSelectorCacheItem(internalSelector )的SelectAction,代码如下:
public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
//再次,该方法里会进行,httpmethod方法匹配
InitializeStandardActions();
//根据请求的Action名称(其次),参数进行匹配过滤(从次)
List<CandidateActionWithParams> selectedCandidates = FindMatchingActions(controllerContext); //最后,匹配结果的异常处理
switch (selectedCandidates.Count)
{
//找不到
case :
throw new HttpResponseException(CreateSelectionError(controllerContext));
case :
//1个结果正常返回
ElevateRouteData(controllerContext, selectedCandidates[]);
return selectedCandidates[].ActionDescriptor;
default:
//匹配了多个结果,抛异常
// Throws exception because multiple actionsByVerb match the request
string ambiguityList = CreateAmbiguousMatchList(selectedCandidates);
throw Error.InvalidOperation(SRResources.ApiControllerActionSelector_AmbiguousMatch, ambiguityList);
}
}
该方法执行完,正常结果会返回一个HttpActionDescriptor ,由于代码细节很多,可以弄到源代码仔细研读。到此代码就执行到了Controller里的ExecuteAsync方法的
HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
//选择Action,通过该语句来触发控制器选择,返回ActionDescriptor
HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
ActionContext.ActionDescriptor = actionDescriptor;
if (Request != null)
{
Request.SetActionDescriptor(actionDescriptor);
}
FilterGrouping filterGrouping = actionDescriptor.GetFilterGrouping(); //ActionFilters
IActionFilter[] actionFilters = filterGrouping.ActionFilters;
//身份认证过滤器
IAuthenticationFilter[] authenticationFilters = filterGrouping.AuthenticationFilters;
//授权过滤器
IAuthorizationFilter[] authorizationFilters = filterGrouping.AuthorizationFilters;
//ExceptionFilters
IExceptionFilter[] exceptionFilters = filterGrouping.ExceptionFilters; IHttpActionResult result = new ActionFilterResult(actionDescriptor.ActionBinding, ActionContext,
controllerServices, actionFilters);
if (authorizationFilters.Length > )
{
result = new AuthorizationFilterResult(ActionContext, authorizationFilters, result);
}
if (authenticationFilters.Length > )
{
result = new AuthenticationFilterResult(ActionContext, this, authenticationFilters, result);
}
if (exceptionFilters.Length > )
{
IExceptionLogger exceptionLogger = ExceptionServices.GetLogger(controllerServices);
IExceptionHandler exceptionHandler = ExceptionServices.GetHandler(controllerServices);
result = new ExceptionFilterResult(ActionContext, exceptionFilters, exceptionLogger, exceptionHandler,
result);
}
//执行IHttpActionResult的ExecuteAsync
return result.ExecuteAsync(cancellationToken);
获取到了ActionDesciptor并放到ActionContext中,也别设置到了Request中,后边就开始获取过滤器了。
ASP.NET Web API 框架研究 Action的选择的更多相关文章
- ASP.NET Web API 框架研究 Action方法介绍
在根据请求解析出匹配的Controller类型并创建实例后,要在该Controller类型中的众多Action方法中选择与请求匹配的那一个,并执行,然后返回响应. Action方法,其元数据,主要包括 ...
- ASP.NET Web API 框架研究 ASP.NET Web API 路由
ASP.NET Web API 核心框架是一个独立的.抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到 ...
- ASP.NET Web API 框架研究 服务容器 ServicesContainer
ServicesContainer是一个服务的容器,可以理解为—个轻量级的IoC容器,其维护着一个服务接口类型与服务实例之间的映射关系,可以根据服务接口类型获取对应的服务实例.构成ASP.NET We ...
- ASP.NET Web API 框架研究 核心的消息处理管道
ASP.NET Web API 的核心框架是一个由一组HttpMessageHandler有序组成的双工消息处理管道:寄宿监听到请求接受后,把消息传入该管道经过所有HttpMessageHandler ...
- ASP.NET Web API 框架研究 Controller实例的销毁
我们知道项目中创建的Controller,如ProductController都继承自ApiController抽象类,其又实现了接口IDisposable,所以,框架中自动调用Dispose方法来释 ...
- ASP.NET Web API 框架研究 Self Host模式下的消息处理管道
Self Host模式下的ASP.NET Web API与WCF非常相似,都可以寄宿在任意类型的托管应用程序中,宿主可以是Windows Form .WPF.控制台应用以及Windows Servic ...
- ASP.NET Web API 框架研究 Web Host模式下的消息处理管道
寄宿的作用是开启一个进程为Web API提供一个运行环境以解决持续监听.请求监听和响应回复,即将接收到的请求转换成HttpRequestMessage对象传入管道,并将管道生成并经过处理后的HttpR ...
- ASP.NET Web API 框架研究 Web Host模式路由及将请求转出到消息处理管道
Web Host 模式下的路由本质上还是通过ASP.NET 路由系统来进行路由的,只是通过继承和组合的方式对ASP.NET路由系统的内部的类进行了一些封装,产生自己专用一套类结构,功能逻辑基本都是一样 ...
- ASP.NET Web API 框架研究 ASP.NET 路由
ASP.NET Web API 如果采用Web Host方式来寄宿,在请求进入Web API 消息处理管道之前,就会用ASP.NET 自身的路由系统根据注册的路由表,解析出当前请求的HttpContr ...
随机推荐
- 异常检测(Anomaly Detection)
十五.异常检测(Anomaly Detection) 15.1 问题的动机 参考文档: 15 - 1 - Problem Motivation (8 min).mkv 在接下来的一系列视频中,我将向大 ...
- 一个基于DPI技术实现了内网资产识别的应用
https://www.forescout.com/products/counteract/see/visibility-capabilities/ Home ≫ Products ≫ ForeSco ...
- Ant.OutputIsUnreadableCode
Ant在Mac OS X终端中的输出乱码的问题 1. 问题: 在用Ant脚本进行构建Android App时,在编译失败时,Ant 输出有乱码. 2. 环境: Mac OS X, 简体中文版.在Ter ...
- [SoapUI] 通过JSONAssert比较两个环境的JSON Response,定制化错误信息到Excel
package ScriptLibrary; import org.json.JSONArray; import org.json.JSONException; import org.json.JSO ...
- 【附源文件】软件工具类Web原型制作分享 - Sketch
Sketch是一款轻量,易用的矢量设计工具.专门为UI设计师开发,让UI设计更简单.更高效. 本原型由国产原型工具-Mockplus制作完成. 非常适合工具类产品官网使用,本模板的交互有通过使用面板组 ...
- socketserver模块实现并发和连接合法性验证
一.socketserver模块 1.sockeserver的源码流程 2.简单的使用 socketserver服务端 import socketserver class MyServer(socke ...
- nginx反向代理缓存服务器的构建
一:代理服务可简单的分为正向代理和反向代理: 正向代理:用于代理内部网络对Internet的连接请求(如VPN/NAT),客户端指定代理服务器,并将本来要直接发送给目标Web服务器的HTTP请求先发送 ...
- 2019.01.24 NOIP训练 旅行(轮廓线dp)
传送门 题意简述: 给一个n∗mn*mn∗m的有障碍的网格图,问你从左上角走到左下角并覆盖所有可行格子的路径条数. 思路: 路径不是很好算. 将图改造一下,在最前面添两列,第一列全部能通过,第二列只有 ...
- hdu-2795(线段树的简单应用)
题目链接:传送门 参考文章:https://blog.csdn.net/qiqi_skystar/article/details/50299743 题意:给出一个高h,宽w的方形画板,有高位1宽为wi ...
- crt转cer ,6.0以上的android系统证书请求配置
1.在服务器人员,给你发送的crt证书后,进到证书路径,执行下面语句 openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der 这样你就可以得到cer ...