asp.net core mvc 3.1 源码分析(六)
通过DefaultActionDescriptorCollectionProvider类,我们获取到了应用的Controller和Action相关信息
后面分析Action是如何执行的
通过IActionSelector找到对应的ActionDescriptor
然后调用IActionInvokerFactory的CreateInvoker的方法创建IActionInvoker对象
internal class ActionInvokerFactory : IActionInvokerFactory { private readonly IActionInvokerProvider[] _actionInvokerProviders; public ActionInvokerFactory(IEnumerable<IActionInvokerProvider> actionInvokerProviders) { _actionInvokerProviders = actionInvokerProviders.OrderBy(item => item.Order).ToArray(); } public IActionInvoker CreateInvoker(ActionContext actionContext) { var context = new ActionInvokerProviderContext(actionContext); foreach (var provider in _actionInvokerProviders) { provider.OnProvidersExecuting(context); } for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--) { _actionInvokerProviders[i].OnProvidersExecuted(context); } return context.Result; } }
internal class ControllerActionInvokerProvider : IActionInvokerProvider { private readonly ControllerActionInvokerCache _controllerActionInvokerCache; private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories; private readonly int _maxModelValidationErrors; private readonly ILogger _logger; private readonly DiagnosticListener _diagnosticListener; private readonly IActionResultTypeMapper _mapper; private readonly IActionContextAccessor _actionContextAccessor; public ControllerActionInvokerProvider( ControllerActionInvokerCache controllerActionInvokerCache, IOptions<MvcOptions> optionsAccessor, ILoggerFactory loggerFactory, DiagnosticListener diagnosticListener, IActionResultTypeMapper mapper) : this(controllerActionInvokerCache, optionsAccessor, loggerFactory, diagnosticListener, mapper, null) { } public ControllerActionInvokerProvider( ControllerActionInvokerCache controllerActionInvokerCache, IOptions<MvcOptions> optionsAccessor, ILoggerFactory loggerFactory, DiagnosticListener diagnosticListener, IActionResultTypeMapper mapper, IActionContextAccessor actionContextAccessor) { _controllerActionInvokerCache = controllerActionInvokerCache; _valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray(); _maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors; _logger = loggerFactory.CreateLogger<ControllerActionInvoker>(); _diagnosticListener = diagnosticListener; _mapper = mapper; _actionContextAccessor = actionContextAccessor ?? ActionContextAccessor.Null; } public int Order => -1000; /// <inheritdoc /> public void OnProvidersExecuting(ActionInvokerProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) { var controllerContext = new ControllerContext(context.ActionContext) { // PERF: These are rarely going to be changed, so let's go copy-on-write. ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories) }; controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; var (cacheEntry, filters) = _controllerActionInvokerCache.GetCachedResult(controllerContext); var invoker = new ControllerActionInvoker( _logger, _diagnosticListener, _actionContextAccessor, _mapper, controllerContext, cacheEntry, filters); context.Result = invoker; } } /// <inheritdoc /> public void OnProvidersExecuted(ActionInvokerProviderContext context) { } }
internal class ControllerActionInvokerCache { private readonly IActionDescriptorCollectionProvider _collectionProvider; private readonly ParameterBinder _parameterBinder; private readonly IModelBinderFactory _modelBinderFactory; private readonly IModelMetadataProvider _modelMetadataProvider; private readonly IFilterProvider[] _filterProviders; private readonly IControllerFactoryProvider _controllerFactoryProvider; private readonly MvcOptions _mvcOptions; private volatile InnerCache _currentCache; public ControllerActionInvokerCache( IActionDescriptorCollectionProvider collectionProvider, ParameterBinder parameterBinder, IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, IEnumerable<IFilterProvider> filterProviders, IControllerFactoryProvider factoryProvider, IOptions<MvcOptions> mvcOptions) { _collectionProvider = collectionProvider; _parameterBinder = parameterBinder; _modelBinderFactory = modelBinderFactory; _modelMetadataProvider = modelMetadataProvider; _filterProviders = filterProviders.OrderBy(item => item.Order).ToArray(); _controllerFactoryProvider = factoryProvider; _mvcOptions = mvcOptions.Value; } private InnerCache CurrentCache { get { var current = _currentCache; var actionDescriptors = _collectionProvider.ActionDescriptors; if (current == null || current.Version != actionDescriptors.Version) { current = new InnerCache(actionDescriptors.Version); _currentCache = current; } return current; } } 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 filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext); filters = filterFactoryResult.Filters; var parameterDefaultValues = ParameterDefaultValues .GetParameterDefaultValues(actionDescriptor.MethodInfo); var objectMethodExecutor = ObjectMethodExecutor.Create( actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo, parameterDefaultValues); var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor); var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor); var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate( _parameterBinder, _modelBinderFactory, _modelMetadataProvider, actionDescriptor, _mvcOptions); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); cacheEntry = new ControllerActionInvokerCacheEntry( filterFactoryResult.CacheableFilters, controllerFactory, controllerReleaser, propertyBinderFactory, objectMethodExecutor, actionMethodExecutor); cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); } else { // Filter instances from statically defined filter descriptors + from filter providers filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters); } return (cacheEntry, filters); } private class InnerCache { public InnerCache(int version) { Version = version; } public ConcurrentDictionary<ActionDescriptor, ControllerActionInvokerCacheEntry> Entries { get; } = new ConcurrentDictionary<ActionDescriptor, ControllerActionInvokerCacheEntry>(); public int Version { get; } } }
internal static class ControllerBinderDelegateProvider { public static ControllerBinderDelegate CreateBinderDelegate( ParameterBinder parameterBinder, IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions) { if (parameterBinder == null) { throw new ArgumentNullException(nameof(parameterBinder)); } if (modelBinderFactory == null) { throw new ArgumentNullException(nameof(modelBinderFactory)); } if (modelMetadataProvider == null) { throw new ArgumentNullException(nameof(modelMetadataProvider)); } if (actionDescriptor == null) { throw new ArgumentNullException(nameof(actionDescriptor)); } if (mvcOptions == null) { throw new ArgumentNullException(nameof(mvcOptions)); } var parameterBindingInfo = GetParameterBindingInfo( modelBinderFactory, modelMetadataProvider, actionDescriptor, mvcOptions); var propertyBindingInfo = GetPropertyBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor); if (parameterBindingInfo == null && propertyBindingInfo == null) { return null; } return Bind; async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments) { var (success, valueProvider) = await CompositeValueProvider.TryCreateAsync(controllerContext, controllerContext.ValueProviderFactories); if (!success) { return; } var parameters = actionDescriptor.Parameters; for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; var bindingInfo = parameterBindingInfo[i]; var modelMetadata = bindingInfo.ModelMetadata; if (!modelMetadata.IsBindingAllowed) { continue; } var result = await parameterBinder.BindModelAsync( controllerContext, bindingInfo.ModelBinder, valueProvider, parameter, modelMetadata, value: null); if (result.IsModelSet) { arguments[parameter.Name] = result.Model; } } var properties = actionDescriptor.BoundProperties; for (var i = 0; i < properties.Count; i++) { var property = properties[i]; var bindingInfo = propertyBindingInfo[i]; var modelMetadata = bindingInfo.ModelMetadata; if (!modelMetadata.IsBindingAllowed) { continue; } var result = await parameterBinder.BindModelAsync( controllerContext, bindingInfo.ModelBinder, valueProvider, property, modelMetadata, value: null); if (result.IsModelSet) { PropertyValueSetter.SetValue(bindingInfo.ModelMetadata, controller, result.Model); } } } } private static BinderItem[] GetParameterBindingInfo( IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions) { var parameters = actionDescriptor.Parameters; if (parameters.Count == 0) { return null; } var parameterBindingInfo = new BinderItem[parameters.Count]; for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; ModelMetadata metadata; if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase && parameter is ControllerParameterDescriptor controllerParameterDescriptor) { // The default model metadata provider derives from ModelMetadataProvider // and can therefore supply information about attributes applied to parameters. metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo); } else { // For backward compatibility, if there's a custom model metadata provider that // only implements the older IModelMetadataProvider interface, access the more // limited metadata information it supplies. In this scenario, validation attributes // are not supported on parameters. metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType); } var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { BindingInfo = parameter.BindingInfo, Metadata = metadata, CacheToken = parameter, }); parameterBindingInfo[i] = new BinderItem(binder, metadata); } return parameterBindingInfo; } private static BinderItem[] GetPropertyBindingInfo( IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor) { var properties = actionDescriptor.BoundProperties; if (properties.Count == 0) { return null; } var propertyBindingInfo = new BinderItem[properties.Count]; var controllerType = actionDescriptor.ControllerTypeInfo.AsType(); for (var i = 0; i < properties.Count; i++) { var property = properties[i]; var metadata = modelMetadataProvider.GetMetadataForProperty(controllerType, property.Name); var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { BindingInfo = property.BindingInfo, Metadata = metadata, CacheToken = property, }); propertyBindingInfo[i] = new BinderItem(binder, metadata); } return propertyBindingInfo; } private readonly struct BinderItem { public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata) { ModelBinder = modelBinder; ModelMetadata = modelMetadata; } public IModelBinder ModelBinder { get; } public ModelMetadata ModelMetadata { get; } } }