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