扒一扒asp.net core mvc控制器的寻找流程

不太会排版,大家将就看吧.

asp.net core mvc和asp.net mvc中都有一个比较有意思的而又被大家容易忽略的功能,控制器可以写在非Web程序集中,比如Web程序集:"MyWeb",引用程序集"B.bll",你可以将所有的控制器写在"B.bll"程序集里面.mvc框架仍然可以寻找到这个控制器.

仔细想一想,mvc框架启动的时候寻找过程:1.找到所有包含控制器的程序集;2.反射找到所有控制器类型;3.反射找到所有的action;4.缓存这些controller与action.

那么有意思的就是第一步"找到所有包含控制器的程序集",一开始我认为是扫描当前应用程序域已加载的程序集,然后反射判断存不存在控制器类型.单如果一个程序有上千个程序集,那么反射无疑是一种灾难,mvc的启动也没这么慢啊,怀着好奇的心,这里就扒一扒官方的实现原理(asp.net mvc源码没去看,但原理估计差不多,这里就扒asp.net core mvc).

 

这里建议大家先了解下 asp.net core mvc的启动流程:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-startup.html.

action的匹配:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html

这里引用杨晓东博客中的图片:

1.AddMvcCore,mvc核心启动代码:

        public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            //获取ApplicationPartManager管理类,该类保存了ApplicationPart集合,而ApplicationPart最重要的子类AssemblyPart记录了程序集信息,另外PopulateFeature用于填充各种功能
            var partManager = GetApplicationPartManager(services);
            services.TryAddSingleton(partManager);

            ConfigureDefaultFeatureProviders(partManager);
            ConfigureDefaultServices(services);
            AddMvcCoreServices(services);

            var builder = new MvcCoreBuilder(services, partManager);

            return builder;
        }

ApplicationPartManager:

    /// <summary>
    /// Manages the parts and features of an MVC application.
    /// </summary>
    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"/>s.
        /// </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);
            }
        }
    }
View Code

 

 private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
        {
            var manager = GetServiceFromCollection<ApplicationPartManager>(services);
            if (manager == null)
            {
                manager = new ApplicationPartManager();

                var environment = GetServiceFromCollection<IHostingEnvironment>(services);
                if (string.IsNullOrEmpty(environment?.ApplicationName))
                {
                    return manager;
                }
                //使用默认的程序集发现提供器查找程序集,这个类就是查找的核心类
                var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
                foreach (var part in parts)
                {
           //将找到的程序集添加到集合中 manager.ApplicationParts.Add(part); } }
return manager; }

 2.DefaultAssemblyPartDiscoveryProvider:

        public static IEnumerable<ApplicationPart> DiscoverAssemblyParts(string entryPointAssemblyName)
        {
//使用应用程序名称加载应用程序的入口程序集
var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); var context = DependencyContext.Load(entryAssembly);
//找到候选的程序集,这里就是"可能"包含了控制器的程序集
return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p)); }

DefaultAssemblyPartDiscoveryProvider代码我就不全贴了(点击查看完整源码),

这里使用了一个DependencyContext 依赖上下文,注意这个依赖上下文不是依赖注入的那个上下文,这个是指程序集引用关系的依赖上下文.

实现原理看起来其实有点low,就是递归计算并判断程序集是否有引用mvc程序集,如果有引用就作为候选程序集.

这里看核心的一个计算方法:

            private DependencyClassification ComputeClassification(string dependency)
            {
                if (!_runtimeDependencies.ContainsKey(dependency))
                {
                    // Library does not have runtime dependency. Since we can't infer
                    // anything about it's references, we'll assume it does not have a reference to Mvc.
                    return DependencyClassification.DoesNotReferenceMvc;
                }

                var candidateEntry = _runtimeDependencies[dependency];
                if (candidateEntry.Classification != DependencyClassification.Unknown)
                {
                    return candidateEntry.Classification;
                }
                else
                {
                    var classification = DependencyClassification.DoesNotReferenceMvc;
                    foreach (var candidateDependency in candidateEntry.Library.Dependencies)
                    {
                        var dependencyClassification = ComputeClassification(candidateDependency.Name);
                        if (dependencyClassification == DependencyClassification.ReferencesMvc ||
                            dependencyClassification == DependencyClassification.MvcReference)
                        {
                            classification = DependencyClassification.ReferencesMvc;
                            break;
                        }
                    }

                    candidateEntry.Classification = classification;

                    return classification;
                }
            }

 

拿到所有候选程序集(就是可能包含控制器的程序集)后,就是调用ApplicationPartManager的PopulateFeature将控制器类型缓存起来.至于后面的action什么的就不再扒了,有兴趣的可以看源代码.

从这个过程可以发现DependencyContext这个类,以及Microsoft.Extensions.DependencyModel这个库,那么我们可以利用这个东西也可以玩出很多花样来.

哦 再补充一个点,我们可以外挂式的加载程序集:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
                .AddApplicationPart(Assembly.LoadFrom(@"C:\demo\demo.dll"));
        }

那么从这个点,你又想到了什么?我想到了.net core mvc插件化的思路. 

posted @ 2018-02-06 17:49  谷草`  阅读(2296)  评论(3编辑  收藏  举报