asp.net core mvc 3.1 源码分析(二)

 看下应用如何构建出对应的ActionDescriptor

DefaultActionDescriptorCollectionProvider

internal class DefaultActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider
    {
        private readonly IActionDescriptorProvider[] _actionDescriptorProviders;
        private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;

        // The lock is used to protect WRITES to the following (do not need to protect reads once initialized).
        private readonly object _lock;
        private ActionDescriptorCollection _collection;
        private IChangeToken _changeToken;
        private CancellationTokenSource _cancellationTokenSource;
        private int _version = 0;

        public DefaultActionDescriptorCollectionProvider(
            IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
            IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
        {
            _actionDescriptorProviders = actionDescriptorProviders
                .OrderBy(p => p.Order)
                .ToArray();

            _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();

            _lock = new object();

            // IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately!
            ChangeToken.OnChange(
                GetCompositeChangeToken,
                UpdateCollection);
        }

        /// <summary>
        /// Returns a cached collection of <see cref="ActionDescriptor" />.
        /// </summary>
        public override ActionDescriptorCollection ActionDescriptors
        {
            get
            {
                Initialize();
                Debug.Assert(_collection != null);
                Debug.Assert(_changeToken != null);

                return _collection;
            }
        }

        /// <summary>
        /// Gets an <see cref="IChangeToken"/> that will be signaled after the <see cref="ActionDescriptors"/>
        /// collection has changed.
        /// </summary>
        /// <returns>The <see cref="IChangeToken"/>.</returns>
        public override IChangeToken GetChangeToken()
        {
            Initialize();
            Debug.Assert(_collection != null);
            Debug.Assert(_changeToken != null);

            return _changeToken;
        }

        private IChangeToken GetCompositeChangeToken()
        {
            if (_actionDescriptorChangeProviders.Length == 1)
            {
                return _actionDescriptorChangeProviders[0].GetChangeToken();
            }

            var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length];
            for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++)
            {
                changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken();
            }

            return new CompositeChangeToken(changeTokens);
        }

        private void Initialize()
        {
            // Using double-checked locking on initialization because we fire change token callbacks
            // when the collection changes. We don't want to do that repeatedly for redundant changes.
            //
            // The main call path of this code on the first call is async initialization from Endpoint Routing
            // which is done in a non-blocking way so in practice no caller will ever block here.
            if (_collection == null)
            {
                lock (_lock)
                {
                    if (_collection == null)
                    {
                        UpdateCollection();
                    }
                }
            }
        }

        private void UpdateCollection()
        {
            // Using the lock to initialize writes means that we serialize changes. This eliminates
            // the potential for changes to be processed out of order - the risk is that newer data
            // could be overwritten by older data.
            lock (_lock)
            {
                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);
                }

                // The sequence for an update is important because we don't want anyone to obtain
                // the new change token but the old action descriptor collection.
                // 1. Obtain the old cancellation token source (don't trigger it yet)
                // 2. Set the new action descriptor collection
                // 3. Set the new change token
                // 4. Trigger the old cancellation token source
                //
                // Consumers who poll will observe a new action descriptor collection at step 2 - they will see
                // the new collection and ignore the change token.
                //
                // Consumers who listen to the change token will re-query at step 4 - they will see the new collection
                // and new change token.
                //
                // Anyone who acquires the collection and change token between steps 2 and 3 will be notified of
                // a no-op change at step 4.

                // Step 1.
                var oldCancellationTokenSource = _cancellationTokenSource;

                // Step 2.
                _collection = new ActionDescriptorCollection(
                    new ReadOnlyCollection<ActionDescriptor>(context.Results),
                    _version++);

                // Step 3.
                _cancellationTokenSource = new CancellationTokenSource();
                _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);

                // Step 4 - might be null if it's the first time.
                oldCancellationTokenSource?.Cancel();
            }
        }
    }

在UpdateCollection方法中,会遍历已经注册IActionDescriptorProvider的服务,分别调用OnProvidersExecuting、OnProvidersExecuted方法构建ActionDescriptor

IActionDescriptorProvider的默认实现类有ControllerActionDescriptorProvider

internal class ControllerActionDescriptorProvider : IActionDescriptorProvider
    {
        private readonly ApplicationPartManager _partManager;
        private readonly ApplicationModelFactory _applicationModelFactory;

        public ControllerActionDescriptorProvider(
            ApplicationPartManager partManager,
            ApplicationModelFactory applicationModelFactory)
        {
            if (partManager == null)
            {
                throw new ArgumentNullException(nameof(partManager));
            }

            if (applicationModelFactory == null)
            {
                throw new ArgumentNullException(nameof(applicationModelFactory));
            }

            _partManager = partManager;
            _applicationModelFactory = applicationModelFactory;
        }

        public int Order => -1000;

        /// <inheritdoc />
        public void OnProvidersExecuting(ActionDescriptorProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            foreach (var descriptor in GetDescriptors())
            {
                context.Results.Add(descriptor);
            }
        }

        /// <inheritdoc />
        public void OnProvidersExecuted(ActionDescriptorProviderContext context)
        {
            // After all of the providers have run, we need to provide a 'null' for each all of route values that
            // participate in action selection.
            //
            // This is important for scenarios like Razor Pages, that use the 'page' route value. An action that
            // uses 'page' shouldn't match when 'action' is set, and an action that uses 'action' shouldn't match when
            // 'page is specified.
            //
            // Or for another example, consider areas. A controller that's not in an area needs a 'null' value for
            // area so it can't match when the route produces an 'area' value.
            var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            for (var i = 0; i < context.Results.Count; i++)
            {
                var action = context.Results[i];
                foreach (var key in action.RouteValues.Keys)
                {
                    keys.Add(key);
                }
            }

            for (var i = 0; i < context.Results.Count; i++)
            {
                var action = context.Results[i];
                foreach (var key in keys)
                {
                    if (!action.RouteValues.ContainsKey(key))
                    {
                        action.RouteValues.Add(key, null);
                    }
                }
            }
        }

        internal IEnumerable<ControllerActionDescriptor> GetDescriptors()
        {
            var controllerTypes = GetControllerTypes();
            var application = _applicationModelFactory.CreateApplicationModel(controllerTypes);
            return ControllerActionDescriptorBuilder.Build(application);
        }

        private IEnumerable<TypeInfo> GetControllerTypes()
        {
            var feature = new ControllerFeature();
            _partManager.PopulateFeature(feature);

            return feature.Controllers;
        }
    }

GetControllerTypes方法获取应用中的Controller类型

这里涉及到ApplicationPartManager对象

public class ApplicationPartManager
    {
        /// <summary>
        /// Gets the list of <see cref="IApplicationFeatureProvider"/>s.
        /// </summary>
        public IList<IApplicationFeatureProvider> FeatureProviders { get; } =
            new List<IApplicationFeatureProvider>();

        /// <summary>
        /// Gets the list of <see cref="ApplicationPart"/> instances.
        /// <para>
        /// Instances in this collection are stored in precedence order. An <see cref="ApplicationPart"/> that appears
        /// earlier in the list has a higher precedence.
        /// An <see cref="IApplicationFeatureProvider"/> may choose to use this an interface as a way to resolve conflicts when
        /// multiple <see cref="ApplicationPart"/> instances resolve equivalent feature values.
        /// </para>
        /// </summary>
        public IList<ApplicationPart> ApplicationParts { get; } = new List<ApplicationPart>();

        /// <summary>
        /// Populates the given <paramref name="feature"/> using the list of
        /// <see cref="IApplicationFeatureProvider{TFeature}"/>s configured on the
        /// <see cref="ApplicationPartManager"/>.
        /// </summary>
        /// <typeparam name="TFeature">The type of the feature.</typeparam>
        /// <param name="feature">The feature instance to populate.</param>
        public void PopulateFeature<TFeature>(TFeature feature)
        {
            if (feature == null)
            {
                throw new ArgumentNullException(nameof(feature));
            }

            foreach (var provider in FeatureProviders.OfType<IApplicationFeatureProvider<TFeature>>())
            {
                provider.PopulateFeature(ApplicationParts, feature);
            }
        }

        internal void PopulateDefaultParts(string entryAssemblyName)
        {
            var assemblies = GetApplicationPartAssemblies(entryAssemblyName);

            var seenAssemblies = new HashSet<Assembly>();

            foreach (var assembly in assemblies)
            {
                if (!seenAssemblies.Add(assembly))
                {
                    // "assemblies" may contain duplicate values, but we want unique ApplicationPart instances.
                    // Note that we prefer using a HashSet over Distinct since the latter isn't
                    // guaranteed to preserve the original ordering.
                    continue;
                }

                var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
                foreach (var applicationPart in partFactory.GetApplicationParts(assembly))
                {
                    ApplicationParts.Add(applicationPart);
                }
            }
        }

        private static IEnumerable<Assembly> GetApplicationPartAssemblies(string entryAssemblyName)
        {
            var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));

            // Use ApplicationPartAttribute to get the closure of direct or transitive dependencies
            // that reference MVC.
            var assembliesFromAttributes = entryAssembly.GetCustomAttributes<ApplicationPartAttribute>()
                .Select(name => Assembly.Load(name.AssemblyName))
                .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal)
                .SelectMany(GetAsemblyClosure);

            // The SDK will not include the entry assembly as an application part. We'll explicitly list it
            // and have it appear before all other assemblies  ApplicationParts.
            return GetAsemblyClosure(entryAssembly)
                .Concat(assembliesFromAttributes);
        }

        private static IEnumerable<Assembly> GetAsemblyClosure(Assembly assembly)
        {
            yield return assembly;

            var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false)
                .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal);

            foreach (var relatedAssembly in relatedAssemblies)
            {
                yield return relatedAssembly;
            }
        }
    }

ApplicationPartManager有IList<IApplicationFeatureProvider>类型的FeatureProviders属性、IList<ApplicationPart>类型的ApplicationParts属性

 ControllerFeatureProvider获取应用中的所有Controller

 

 从ApplicationParts加载所有的Controller

然后调用ApplicationModelFactory类的CreateApplicationModel创建ApplicationModel对象

 遍历注册的IApplicationModelProvider服务,调用OnProvidersExecuting、OnProvidersExecuted创建ApplicationModel

再调用ApplicationModelConventions的ApplyConventions

创建好ApplicationModel之后,再调用ControllerActionDescriptorBuilder类的Build方法创建ControllerActionDescriptor列表

原文地址:https://www.cnblogs.com/lanpingwang/p/12642472.html