目录
-
ApiController
-
HttpActionDescriptor
-
IHttpActionSelector
ApiController
在上节中,讲到如何选择并激活对应的IHttpController,而一般我们在开发中使用的是ApiController
public abstract class ApiController : IHttpController, IDisposable
{
public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
this.Initialize(controllerContext);
HttpActionDescriptor actionDescriptor = services.GetActionSelector().SelectAction(controllerContext);
HttpActionContext actionContext = new HttpActionContext(controllerContext,actionDescriptor);
return services.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken);
}
}
在ApiController中,我们看到通过内置的DI容器选择出对应的HttpActionDescriptor.本节重点内容就是介绍SelectAction方法.
HttpActionDescriptor
在介绍IHttpActionSelector前,我们需要了解HttpActionDescriptor
public abstract class HttpActionDescriptor
{
//相关联的HttpControllerDescriptor
public HttpControllerDescriptor ControllerDescriptor { get; } { set; }
//Action名称(通过使用ActionName特性,Action名称可以和方法名称不同)
public abstract string ActionName { get; }
//Action返回值类型(void为null)
public abstract Type ReturnType { get; }
//支持的HttpMethod(默认一个Action方法支持一种HttpMethod,且默认为Post Method,可以使用AcceptVerbs特性支持多个)
public virtual Collection<HttpMethod> SupportedHttpMethods { get; }
//Action方法所有参数
public abstract Collection<HttpParameterDescriptor> Parameters { get; };
}
而HttpActionDescriptor默认实现为ReflectedHttpActionDescriptor,这里稍微展示下初始化HttpActionDescriptor过程
public class ReflectedHttpActionDescriptor : HttpActionDescriptor
{
//初始化HttpActionDescriptor的属性
public ReflectedHttpActionDescriptor(HttpControllerDescriptor controllerDescriptor, MethodInfo methodInfo)
: base(controllerDescriptor)
{
this.InitializeProperties(methodInfo);
this._parameters = new Lazy<Collection<HttpParameterDescriptor>>(() => this.InitializeParameterDescriptors());
}
private Collection<HttpParameterDescriptor> InitializeParameterDescriptors()
{
return this.ParameterInfos.Select(item => new ReflectedHttpParameterDescriptor((HttpActionDescriptor) this, item)).ToList();
}
}
IHttpActionSelector
IHttpActionSelector是选择Action最核心的类
public interface IHttpActionSelector
{
//选择符合标准的Action
HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
//获取HttpController所有Action
ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
}
IHttpActionSelector默认的实现为ApiControllerActionSelector
public class ApiControllerActionSelector : IHttpActionSelector
{
ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
{
//找到所有符合要求的方法
var methods = controllerDescriptor.ControllerType
.GetMethods(BindingFlags.Instance | BindingFlags.Public), methodInfo =>
!methodInfo.IsSpecialName && !methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(TypeHelper.ApiControllerType)
&& methodInfo.GetCustomAttribute<NonActionAttribute>() == null);
return methods.Select(method => new HttpActionDescriptor(controllerDescriptor,method))...;
}
}
注意:GetActionMapping会在请求每个HttpController第一次的时候 缓存当前所有HttpActionDescriptor
当我们找到HttpController下所有HttpActionDescriptor,还需要最后一步通过SelectAction筛选出最终的Action
由于这块源码稍微复杂,这里把关键的几步及对应的方法名说明下
public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
1. Action名称过滤
如果路由中配置Action变量,则会先过滤ActionName.调用方法GetInitialCandidateList
2. Http方法过滤
对于Get Put Post默认都通过,调用方法FindActionsForVerb
3. 必须的参数能绑定上
在Action方法上的必须参数,在路由参数和Request参数中能获取到,调用方法FindActionMatchRequiredRouteAndQueryParameters
4. 参数个数符合最多的匹配
当多个Action方法都满足以上3个条件时,取最多参数符合的那个,调用方法FindActionMatchMostRouteAndQueryParameters
5. 取唯一匹配
最终匹配结果为1个Action方法则成功,多个或零失败,在SelectAction方法本身调用
}
这里稍微举个例子来解释SelectAction
public class DemoController : ApiController
{
public string Get(int x)
public string Get(int x, int y)
public string Get(string x, string y)
public string Get(string x, string y, string z)
}
分别请求如下地址,并列出对应的匹配方法
-
demo?x=1 OK
public string Get(int x)
-
demo?x=1&y=2 Erro
public string Get(int x, int y)
public string Get(string x, string y)
-
demo?x=1&y=2&z=3 OK
public string Get(string x, string y, string z)
(OK表示请求成功,Erro表示请求失败)
备注:
- Action的选择明显比Controller的激活要复杂
- 文章中的代码并非完整WebAPI代码,一般是经过自己精简后的.
- 本篇内容使用MarkDown语法编辑