Asp.net web Api源码分析HttpActionDescriptor的创建

紧接着上文Asp.net web Api源码分析-HttpControllerDispatcher (Controller的创建)这里已经创建好了IHttpController,现在让我们来看看它的ExecuteAsync方法,这个方法很是复杂啊。

 public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
        {
            if (_request != null)
            {
                // if user has registered a controller factory which produces the same controller instance, we should throw here
                throw Error.InvalidOperation(SRResources.CannotSupportSingletonInstance, typeof(ApiController).Name, typeof(IHttpControllerActivator).Name);
            }

            Initialize(controllerContext);

            // We can't be reused, and we know we're disposable, so make sure we go away when
            // the request has been completed.
            if (_request != null)
            {
                _request.RegisterForDispose(this);
            }

            HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
            ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
            HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
            HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);

            IEnumerable<FilterInfo> filters = actionDescriptor.GetFilterPipeline();

            FilterGrouping filterGrouping = new FilterGrouping(filters);

            IEnumerable<IActionFilter> actionFilters = filterGrouping.ActionFilters;
            IEnumerable<IAuthorizationFilter> authorizationFilters = filterGrouping.AuthorizationFilters;
            IEnumerable<IExceptionFilter> exceptionFilters = filterGrouping.ExceptionFilters;

            // Func<Task<HttpResponseMessage>>
            Task<HttpResponseMessage> result = InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, () =>
            {
                HttpActionBinding actionBinding = actionDescriptor.ActionBinding;
                Task bindTask = actionBinding.ExecuteBindingAsync(actionContext, cancellationToken);
                return bindTask.Then<HttpResponseMessage>(() =>
                {
                    _modelState = actionContext.ModelState;
                    Func<Task<HttpResponseMessage>> invokeFunc = InvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =>
                    {
                        return controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken);
                    });
                    return invokeFunc();
                });
            })();

            result = InvokeActionWithExceptionFilters(result, actionContext, cancellationToken, exceptionFilters);

            return result;
        }

首先调用Initialize方法初始化ControllerContext、_request、_configuration,紧接着就创建一个HttpActionDescriptor

 HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);

   在DefaultServices中有这么一句SetSingle<IHttpActionSelector>(new ApiControllerActionSelector());, 所以我们就知道controllerServices.GetActionSelector()返回的是一个 ApiControllerActionSelector实例。其中ApiControllerActionSelector的SelectAction 实现也比较简单:

    public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
        {
            ActionSelectorCacheItem internalSelector = GetInternalSelector(controllerContext.ControllerDescriptor);
            return internalSelector.SelectAction(controllerContext);

        }

我们首先来看看GetInternalSelector方法实现:、

   private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor)
        {
            // First check in the local fast cache and if not a match then look in the broader
            // HttpControllerDescriptor.Properties cache
            if (_fastCache == null)
            {
                ActionSelectorCacheItem selector = new ActionSelectorCacheItem(controllerDescriptor);
                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
            {
                // If the key doesn't match then lookup/create delegate in the HttpControllerDescriptor.Properties for
                // that HttpControllerDescriptor instance
                ActionSelectorCacheItem selector = (ActionSelectorCacheItem)controllerDescriptor.Properties.GetOrAdd(
                    _cacheKey,
                    _ => new ActionSelectorCacheItem(controllerDescriptor));
                return selector;
            }
        }

说白了就是直接实例化一个ActionSelectorCacheItem,让我们来看看ActionSelectorCacheItem的构造函数:

  public ActionSelectorCacheItem(HttpControllerDescriptor controllerDescriptor)
            {
                Contract.Assert(controllerDescriptor != null);

                // Initialize the cache entirely in the ctor on a single thread.
                _controllerDescriptor = controllerDescriptor;

                MethodInfo[] allMethods = _controllerDescriptor.ControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
                MethodInfo[] validMethods = Array.FindAll(allMethods, IsValidActionMethod);

                _actionDescriptors = new ReflectedHttpActionDescriptor[validMethods.Length];
                for (int i = 0; i < validMethods.Length; i++)
                {
                    MethodInfo method = validMethods[i];
                    ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(_controllerDescriptor, method);
                    _actionDescriptors[i] = 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.IsSimpleUnderlyingType(binding.Descriptor.ParameterType) && binding.WillReadUri())
                            .Select(binding => binding.Descriptor.Prefix ?? binding.Descriptor.ParameterName).ToArray());
                }

                _actionNameMapping = _actionDescriptors.ToLookup(actionDesc => actionDesc.ActionName, StringComparer.OrdinalIgnoreCase);

                // Bucket the action descriptors by common verbs. 
                int len = _cacheListVerbKinds.Length;
                _cacheListVerbs = new ReflectedHttpActionDescriptor[len][];
                for (int i = 0; i < len; i++)
                {
                    _cacheListVerbs[i] = FindActionsForVerbWorker(_cacheListVerbKinds[i]);
                }
            }

 首先根据 _controllerDescriptor.ControllerType来获取所有共有、实例方法,然后过滤掉那些特殊的方法最后得到一个 可以从当Action的方法集合validMethods。循环集合中的每一个方法,依次处理。首先我们实例化一个 ReflectedHttpActionDescriptor,采用以下代码:

     ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(_controllerDescriptor, method);

现在我们来看看ReflectedHttpActionDescriptor的构造函数有什么特别的:

  public class ReflectedHttpActionDescriptor : HttpActionDescriptor{
    public ReflectedHttpActionDescriptor(HttpControllerDescriptor controllerDescriptor, MethodInfo methodInfo)
            : base(controllerDescriptor)
        {
            InitializeProperties(methodInfo);
            _parameters = new Lazy<Collection<HttpParameterDescriptor>>(() => InitializeParameterDescriptors());
        }
		  private void InitializeProperties(MethodInfo methodInfo)
        {
            _methodInfo = methodInfo;
            _returnType = GetReturnType(methodInfo);
            _actionExecutor = new Lazy<ActionExecutor>(() => InitializeActionExecutor(_methodInfo));
            _attrCached = _methodInfo.GetCustomAttributes(inherit: true);
            CacheAttrsIActionMethodSelector = _attrCached.OfType<IActionMethodSelector>().ToArray();
            _actionName = GetActionName(_methodInfo, _attrCached);
            _supportedHttpMethods = GetSupportedHttpMethods(_methodInfo, _attrCached);
        }
		  private Collection<HttpParameterDescriptor> InitializeParameterDescriptors()
        {
            Contract.Assert(_methodInfo != null);

            List<HttpParameterDescriptor> parameterInfos = _methodInfo.GetParameters().Select(
                (item) => new ReflectedHttpParameterDescriptor(this, item)).ToList<HttpParameterDescriptor>();
            return new Collection<HttpParameterDescriptor>(parameterInfos);
        }

  }
  public abstract class HttpActionDescriptor{
   protected HttpActionDescriptor()
        {
            _filterPipeline = new Lazy<Collection<FilterInfo>>(InitializeFilterPipeline);
        }

        protected HttpActionDescriptor(HttpControllerDescriptor controllerDescriptor)
            : this()
        {
            if (controllerDescriptor == null)
            {
                throw Error.ArgumentNull("controllerDescriptor");
            }

            _controllerDescriptor = controllerDescriptor;
            _configuration = _controllerDescriptor.Configuration;
        }
		  public virtual HttpActionBinding ActionBinding
        {
            get
            {
                if (_actionBinding == null)
                {
                    ServicesContainer controllerServices = _controllerDescriptor.Configuration.Services;
                    IActionValueBinder actionValueBinder = controllerServices.GetActionValueBinder();
                    HttpActionBinding actionBinding = actionValueBinder.GetBinding(this);
                    _actionBinding = actionBinding;
                }
                return _actionBinding;
            }
            set
            {
                if (value == null)
                {
                    throw Error.PropertyNull();
                }
                _actionBinding = value;
            }
        }


  }

 这里ReflectedHttpActionDescriptor首先调用父类HttpActionDescriptor的构造函数创建一个 FilterInfo的集合,然后调用自己的InitializeProperties来初始化一些变量,最后在调用 InitializeParameterDescriptors来设置参数。这里父类的InitializeFilterPipeline方法我们暂时忽 略它,在后面要用的时候在来说它。在InitializeProperties方法中这几句都比较重要首先设置方法的返回值(  _returnType = GetReturnType(methodInfo);)和执行函数(new Lazy<ActionExecutor>(() => InitializeActionExecutor(_methodInfo));和mvc源码一样),然后是获取方法的Attribute特性,后面的几句也没什么特别的到用的时候在说吧,默认我们是没有什么Attributer特性,其 中GetSupportedHttpMethods方法主要是检查当前的方法支持哪些http请求处理,如果方法没有 IActionHttpMethodProvider特性,那么我们就根据方法的名称来处理,看方法是以什么打头的 (Get,Post,Put,Delete、Head、Options),这里 InitializeParameterDescriptors方法也比较简单,把每个方法的每个参数有创建一个 ReflectedHttpParameterDescriptor实例,ReflectedHttpParameterDescriptor的构造方法 也没什么特殊的。

现在我们再来看看HttpActionDescriptor的ActionBinding属性,

 ServicesContainer controllerServices = _controllerDescriptor.Configuration.Services;
  IActionValueBinder actionValueBinder = controllerServices.GetActionValueBinder();
  HttpActionBinding actionBinding = actionValueBinder.GetBinding(this);

我们可以知道默认的controllerServices是DefaultServices,所以这里的actionValueBinder其实是DefaultActionValueBinder实例,这里的ActionBinding主要是涉及到参数绑定的时候需要,暂时忽略它吧。

现在让我们回到ActionSelectorCacheItem的构造方法中来,这里的_actionParameterNames、_actionNameMapping我们都知道它是什么东东了,

最后这里还有这么一句

_cacheListVerbs = new ReflectedHttpActionDescriptor[len][];
                for (int i = 0; i < len; i++)
                {
                    _cacheListVerbs[i] = FindActionsForVerbWorker(_cacheListVerbKinds[i]);
                }

这里的主要目标就是把当前的Controller中的所有Action给分成3类,它们分别支持Get、put、post请求,美一类都可能有多个方法。

现在让我们来看看ActionSelectorCacheItem的SelectAction方法:

  public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
            {
                string actionName;
                bool useActionName = controllerContext.RouteData.Values.TryGetValue(ActionRouteKey, out actionName);

                ReflectedHttpActionDescriptor[] actionsFoundByHttpMethods;

                HttpMethod incomingMethod = controllerContext.Request.Method;

                // First get an initial candidate list. 
                if (useActionName)
                {
                    // We have an explicit {action} value, do traditional binding. Just lookup by actionName
                    ReflectedHttpActionDescriptor[] actionsFoundByName = _actionNameMapping[actionName].ToArray();

                    // Throws HttpResponseException with NotFound status because no action matches the Name
                    if (actionsFoundByName.Length == 0)
                    {
                        throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(
                            HttpStatusCode.NotFound,
                            Error.Format(SRResources.ResourceNotFound, controllerContext.Request.RequestUri),
                            Error.Format(SRResources.ApiControllerActionSelector_ActionNameNotFound, _controllerDescriptor.ControllerName, actionName)));
                    }

                    // This filters out any incompatible verbs from the incoming action list
                    actionsFoundByHttpMethods = actionsFoundByName.Where(actionDescriptor => actionDescriptor.SupportedHttpMethods.Contains(incomingMethod)).ToArray();
                }
                else
                {
                    // No {action} parameter, infer it from the verb.
                    actionsFoundByHttpMethods = FindActionsForVerb(incomingMethod);
                }

                // Throws HttpResponseException with MethodNotAllowed status because no action matches the Http Method
                if (actionsFoundByHttpMethods.Length == 0)
                {
                    throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(
                        HttpStatusCode.MethodNotAllowed,
                        Error.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, incomingMethod)));
                }

                // Make sure the action parameter matches the route and query parameters. Overload resolution logic is applied when needed.
                IEnumerable<ReflectedHttpActionDescriptor> actionsFoundByParams = FindActionUsingRouteAndQueryParameters(controllerContext, actionsFoundByHttpMethods, useActionName);

                List<ReflectedHttpActionDescriptor> selectedActions = RunSelectionFilters(controllerContext, actionsFoundByParams);
                actionsFoundByHttpMethods = null;
                actionsFoundByParams = null;

                switch (selectedActions.Count)
                {
                    case 0:
                        // Throws HttpResponseException with NotFound status because no action matches the request
                        throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(
                            HttpStatusCode.NotFound,
                            Error.Format(SRResources.ResourceNotFound, controllerContext.Request.RequestUri),
                            Error.Format(SRResources.ApiControllerActionSelector_ActionNotFound, _controllerDescriptor.ControllerName)));
                    case 1:
                        return selectedActions[0];
                    default:
                        // Throws exception because multiple actions match the request
                        string ambiguityList = CreateAmbiguousMatchList(selectedActions);
                        throw Error.InvalidOperation(SRResources.ApiControllerActionSelector_AmbiguousMatch, ambiguityList);
                }
            }

 这里首先获取当前http请求方法(get、put、post) 然后从当前路由信息里面去查找Action参数,如果找到则通过_actionNameMapping[actionName].ToArray() 来获取当前的Action集合在过滤掉符合此次http请求的Action,否则调用调 用 FindActionsForVerb(incomingMethod)来获取Action集合,FindActionsForVerb方法默认会现在_cacheListVerbs中根据http请求类型来找Action集合,如果找到则返回,否者查找符合条件的整个Controller中的Action。 现在查找到的Action都在actionsFoundByHttpMethods里面,我们需要过滤掉这个Action集合,只能从这个集合中返回一个 Action,这里我们首先用FindActionUsingRouteAndQueryParameters方法来过滤,

  private IEnumerable<ReflectedHttpActionDescriptor> FindActionUsingRouteAndQueryParameters(HttpControllerContext controllerContext, IEnumerable<ReflectedHttpActionDescriptor> actionsFound, bool hasActionRouteKey)
            {
                IDictionary<string, object> routeValues = controllerContext.RouteData.Values;
                HashSet<string> routeParameterNames = new HashSet<string>(routeValues.Keys, StringComparer.OrdinalIgnoreCase);
                routeParameterNames.Remove(ControllerRouteKey);
                if (hasActionRouteKey)
                {
                    routeParameterNames.Remove(ActionRouteKey);
                }

                HttpRequestMessage request = controllerContext.Request;
                Uri requestUri = request.RequestUri;
                bool hasQueryParameters = requestUri != null && !String.IsNullOrEmpty(requestUri.Query);
                bool hasRouteParameters = routeParameterNames.Count != 0;

                if (hasRouteParameters || hasQueryParameters)
                {
                    var combinedParameterNames = new HashSet<string>(routeParameterNames, StringComparer.OrdinalIgnoreCase);
                    if (hasQueryParameters)
                    {
                        foreach (var queryNameValuePair in request.GetQueryNameValuePairs())
                        {
                            combinedParameterNames.Add(queryNameValuePair.Key);
                        }
                    }

                    // action parameters is a subset of route parameters and query parameters
                    actionsFound = actionsFound.Where(descriptor => IsSubset(_actionParameterNames[descriptor], combinedParameterNames));

                    if (actionsFound.Count() > 1)
                    {
                        // select the results that match the most number of required parameters 
                        actionsFound = actionsFound
                            .GroupBy(descriptor => _actionParameterNames[descriptor].Length)
                            .OrderByDescending(g => g.Key)
                            .First();
                    }
                }
                else
                {
                    // return actions with no parameters
                    actionsFound = actionsFound.Where(descriptor => _actionParameterNames[descriptor].Length == 0);
                }

                return actionsFound;
            }

 这个方法代码有点多,其实很简单的,首先整合controllerContext.RouteData.Values和 request.GetQueryNameValuePairs()中的数据为combinedParameterNames,然后看action中的每 个参数是否都能在combinedParameterNames里面找到,如果找到则把结果放到actionsFound集合中来,如果 actionsFound集合中Action多余一个,则我们取参数最后的那个Action,同样这里如果combinedParameterNames 没有数据,则我们就返回一个没有参数的Action。通过FindActionUsingRouteAndQueryParameters方法我们可以从一大堆的Action中获取一个Action
紧随其后我们采用RunSelectionFilters方法来验证我们的Action方法是否有效,如果Action没有 IActionMethodSelector特性,则直接返回,否者依次调用IActionMethodSelector的 IsValidForRequest来验证这个Action是否合法,最后返回合法的Action。其具体实现如下:

 private static List<ReflectedHttpActionDescriptor> RunSelectionFilters(HttpControllerContext controllerContext, IEnumerable<HttpActionDescriptor> descriptorsFound)
            {
                // remove all methods which are opting out of this request
                // to opt out, at least one attribute defined on the method must return false

                List<ReflectedHttpActionDescriptor> matchesWithSelectionAttributes = null;
                List<ReflectedHttpActionDescriptor> matchesWithoutSelectionAttributes = new List<ReflectedHttpActionDescriptor>();

                foreach (ReflectedHttpActionDescriptor actionDescriptor in descriptorsFound)
                {
                    IActionMethodSelector[] attrs = actionDescriptor.CacheAttrsIActionMethodSelector;
                    if (attrs.Length == 0)
                    {
                        matchesWithoutSelectionAttributes.Add(actionDescriptor);
                    }
                    else
                    {
                        bool match = Array.TrueForAll(attrs, selector => selector.IsValidForRequest(controllerContext, actionDescriptor.MethodInfo));
                        if (match)
                        {
                            if (matchesWithSelectionAttributes == null)
                            {
                                matchesWithSelectionAttributes = new List<ReflectedHttpActionDescriptor>();
                            }
                            matchesWithSelectionAttributes.Add(actionDescriptor);
                        }
                    }
                }

                // if a matching action method had a selection attribute, consider it more specific than a matching action method
                // without a selection attribute
                if ((matchesWithSelectionAttributes != null) && (matchesWithSelectionAttributes.Count > 0))
                {
                    return matchesWithSelectionAttributes;
                }
                else
                {
                    return matchesWithoutSelectionAttributes;
                }
            }

 回到我们的SelectAction方法中来,最后返回我们找到的ReflectedHttpActionDescriptor实例,如果没找到或找到多 个都抛出异常,这里个人建议我们在路由中尽力用到Action参数,且每个Action的名称不同,为了和默认的编码一直我们的Action也尽力用 Get,put、post、Delete打头。在前面我们获取ControllerType时是有缓存的,根据ControllerType来创建实例也 是有缓存的只不过缓存的是一个表达式数,这里的ActionSelectorCacheItem也是有缓存的,在mvc中也有类似的缓存。 到这里我们的HttpActionDescriptor创建就结束了。

原文地址:https://www.cnblogs.com/majiang/p/2802113.html