在敘述 "Controller" 一文中,有一處未做解釋,即CreateControllerFactory方法中ControllerActionDescriptor參數是如何產生的。這是因為其與Action的關聯性更大,所以放在本文中繼續描述。 回到MvcRouteHandler或者MvcAttri ...
在敘述Controller一文中,有一處未做解釋,即CreateControllerFactory方法中ControllerActionDescriptor參數是如何產生的。這是因為其與Action的關聯性更大,所以放在本文中繼續描述。
回到MvcRouteHandler或者MvcAttributeRouteHandler的方法中:
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中被篩選出來的。
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類。
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方法。
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之中。
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。
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。
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的屬性。
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的。
先找到創建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類中探尋,主要是涉及反射相關的知識,這裡就不做進一步解釋了。