緊接着上文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創建就結束了。