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

2023-04-25,,

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

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);
}
}
}
 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插件化的思路.

扒一扒asp.net core mvc控制器的寻找流程的相关教程结束。

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

下载本文的Word格式文档,以方便收藏与打印。