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

时间:2023-03-09 00:03:35
扒一扒asp.net core mvc控制器的寻找流程

不太会排版,大家将就看吧扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒asp.net core mvc控制器的寻找流程扒一扒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

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

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

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