在敘述Controller一文中,有一處未作解釋,即CreateControllerFactory方法中ControllerActionDescriptor參數是如何產生的。這是由於其與Action的關聯性更大,因此放在本文中繼續描述。html
回到MvcRouteHandler或者MvcAttributeRouteHandler的方法中:app
public Task RouteAsync(RouteContext context) { ... var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } return invoker.InvokeAsync(); }; ... }
不難發現做爲源頭的ActionContext中傳入了actionDescriptor,而這個參數的值是在ActionSelector中被篩選出來的。async
public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context) { ... var cache = Current; // The Cache works based on a string[] of the route values in a pre-calculated order. This code extracts // those values in the correct order. var keys = cache.RouteKeys; var values = new string[keys.Length]; for (var i = 0; i < keys.Length; i++) { context.RouteData.Values.TryGetValue(keys[i], out object value); if (value != null) { values[i] = value as string ?? Convert.ToString(value); } } if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) || cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues)) { Debug.Assert(matchingRouteValues != null); return matchingRouteValues; } _logger.NoActionsMatched(context.RouteData.Values); return EmptyActions; }
而後可供篩選的ActionDescriptors集合又是來自ActionDescriptorCollectionProvider類。ide
private Cache Current { get { var actions = _actionDescriptorCollectionProvider.ActionDescriptors; var cache = Volatile.Read(ref _cache); if (cache != null && cache.Version == actions.Version) { return cache; } cache = new Cache(actions); Volatile.Write(ref _cache, cache); return cache; } }
它的內部又再調用了ControllerActionDescriptorProvider類的OnProvidersExecuting方法。ui
public ActionDescriptorCollection ActionDescriptors { get { if (_collection == null) { UpdateCollection(); } return _collection; } } private void UpdateCollection() { var context = new ActionDescriptorProviderContext(); for (var i = 0; i < _actionDescriptorProviders.Length; i++) { _actionDescriptorProviders[i].OnProvidersExecuting(context); } for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--) { _actionDescriptorProviders[i].OnProvidersExecuted(context); } _collection = new ActionDescriptorCollection( new ReadOnlyCollection<ActionDescriptor>(context.Results), Interlocked.Increment(ref _version)); }
調用鏈繼續深刻到DefaultApplicationModelProvider之中。this
public void OnProvidersExecuting(ActionDescriptorProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } foreach (var descriptor in GetDescriptors()) { context.Results.Add(descriptor); } } protected internal IEnumerable<ControllerActionDescriptor> GetDescriptors() { var applicationModel = BuildModel(); ApplicationModelConventions.ApplyConventions(applicationModel, _conventions); return ControllerActionDescriptorBuilder.Build(applicationModel); } protected internal ApplicationModel BuildModel() { var controllerTypes = GetControllerTypes(); var context = new ApplicationModelProviderContext(controllerTypes); for (var i = 0; i < _applicationModelProviders.Length; i++) { _applicationModelProviders[i].OnProvidersExecuting(context); } for (var i = _applicationModelProviders.Length - 1; i >= 0; i--) { _applicationModelProviders[i].OnProvidersExecuted(context); } return context.Result; } private IEnumerable<TypeInfo> GetControllerTypes() { var feature = new ControllerFeature(); _partManager.PopulateFeature(feature); return feature.Controllers; }
到了這裏終於能夠看到Action的影子,雖然如今還只是ActionModel。pwa
public virtual void OnProvidersExecuting(ApplicationModelProviderContext context) { ... foreach (var controllerType in context.ControllerTypes) { var controllerModel = CreateControllerModel(controllerType); if (controllerModel == null) { continue; } context.Result.Controllers.Add(controllerModel); controllerModel.Application = context.Result; ... foreach (var methodInfo in controllerType.AsType().GetMethods()) { var actionModel = CreateActionModel(controllerType, methodInfo); if (actionModel == null) { continue; } actionModel.Controller = controllerModel; controllerModel.Actions.Add(actionModel); foreach (var parameterInfo in actionModel.ActionMethod.GetParameters()) { var parameterModel = CreateParameterModel(parameterInfo); if (parameterModel != null) { parameterModel.Action = actionModel; actionModel.Parameters.Add(parameterModel); } } } } }
利用ControllerActionDescriptorBuilder類的Build方法,能夠獲得預期的ControllerActionDescriptor。code
public static IList<ControllerActionDescriptor> Build(ApplicationModel application) { var actions = new List<ControllerActionDescriptor>(); var methodInfoMap = new MethodToActionMap(); var routeTemplateErrors = new List<string>(); var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>(); foreach (var controller in application.Controllers) { // Only add properties which are explicitly marked to bind. // The attribute check is required for ModelBinder attribute. var controllerPropertyDescriptors = controller.ControllerProperties .Where(p => p.BindingInfo != null) .Select(CreateParameterDescriptor) .ToList(); foreach (var action in controller.Actions) { // Controllers with multiple [Route] attributes (or user defined implementation of // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider // instance. // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations // have already been identified as different actions during action discovery. var actionDescriptors = CreateActionDescriptors(application, controller, action); foreach (var actionDescriptor in actionDescriptors) { actionDescriptor.ControllerName = controller.ControllerName; actionDescriptor.ControllerTypeInfo = controller.ControllerType; AddApiExplorerInfo(actionDescriptor, application, controller, action); AddRouteValues(actionDescriptor, controller, action); AddProperties(actionDescriptor, action, controller, application); actionDescriptor.BoundProperties = controllerPropertyDescriptors; if (IsAttributeRoutedAction(actionDescriptor)) { // Replaces tokens like [controller]/[action] in the route template with the actual values // for this action. ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors); } } methodInfoMap.AddToMethodInfo(action, actionDescriptors); actions.AddRange(actionDescriptors); } } ... return actions; }
ControllerActionDescriptor包含了足以構建Controller與Action的屬性。orm
public string ControllerName { get; set; } public virtual string ActionName { get; set; } public MethodInfo MethodInfo { get; set; } public TypeInfo ControllerTypeInfo { get; set; } public IList<ParameterDescriptor> Parameters { get; set; }
Controller的構建已經介紹過了,如今該談談關於Action的。htm
先找到建立ControllerActionInvokerCacheEntry對象的ControllerActionInvokerCache類的GetCachedResult方法。能夠看到兩個關鍵參數objectMethodExecutor與actionMethodExecutor的建立方式。
public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext) { var cache = CurrentCache; var actionDescriptor = controllerContext.ActionDescriptor; IFilterMetadata[] filters; if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) { ... var objectMethodExecutor = ObjectMethodExecutor.Create( actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo, parameterDefaultValues); ... var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); cacheEntry = new ControllerActionInvokerCacheEntry( filterFactoryResult.CacheableFilters, controllerFactory, controllerReleaser, propertyBinderFactory, objectMethodExecutor, actionMethodExecutor); cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); } ... return (cacheEntry, filters); }
再到ControllerActionInvoker類的Next方法中跟蹤到State.ActionInside環節:
case State.ActionInside: { var task = InvokeActionMethodAsync(); if (task.Status != TaskStatus.RanToCompletion) { next = State.ActionEnd; return task; } goto case State.ActionEnd; }
終於能夠找到建立Action的方法。
private async Task InvokeActionMethodAsync() { var controllerContext = _controllerContext; var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor; var controller = _instance; var arguments = _arguments; var actionMethodExecutor = _cacheEntry.ActionMethodExecutor; var orderedArguments = PrepareArguments(arguments, objectMethodExecutor); var diagnosticSource = _diagnosticSource; var logger = _logger; IActionResult result = null; try { diagnosticSource.BeforeActionMethod( controllerContext, arguments, controller); logger.ActionMethodExecuting(controllerContext, orderedArguments); var stopwatch = ValueStopwatch.StartNew(); var actionResultValueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments); if (actionResultValueTask.IsCompletedSuccessfully) { result = actionResultValueTask.Result; } else { result = await actionResultValueTask; } _result = result; logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime()); } ... }
核心的代碼是這一句actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments)
。
actionMethodExecutor與objectMethodExecutor便是以前生成ControllerActionInvokerCacheEntry對象時傳入的兩個參數,controller是在State.ActionBegin環節經過_instance = _cacheEntry.ControllerFactory(controllerContext);
生成的。orderedArguments是Action方法所需的參數。
至於更詳細的建立過程,能夠到ActionMethodExecutor類與ObjectMethodExecutor類中探尋,主要是涉及反射相關的知識,這裏就不作進一步解釋了。