原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore
发布于:2017年3月
环境:ASP.NET Core 1.1json
欢迎阅读新系列的第一部分,我将剖析MVC源代码,给你们展现隐藏在表面之下的工做机制。此系列将分析MVC的内部,若是以为枯燥,能够中止阅读。但就我我的而言,也是通过反复阅读、调试甚至抓狂,直到最后理解ASP.NET MVC源代码(或者自认为理解),从中获益匪浅。经过了解框架的运做机制,咱们能够更好的使用它们,更容易解决遇到的问题。mvc
我会尽力给你们解释对源码的理解,我不能保证本身的理解和解释是100%正确,但我会竭尽所能。要知道简洁清晰的把一段代码解释清楚是很困难的,我将经过小块代码展现MVC源代码,并附源文件连接,方便你们追踪。阅读以后若是仍不理解,我建议你花些时间读读源代码,必要时亲自动手调试一下。我但愿此系列会引发像我同样喜欢刨根问底的人的兴趣。app
本文我将剖析AddMvcCore到底为咱们作了什么,同时关注几个像 ApplicationPartManager这样的类。本文使用的project.json基于rel/1.1.2源代码,经过运行MVC Sandbox 项目进行调试。框架
注:MvcSandbox为ASP.Net Core MVC源码中的示列项目。 |
因为版本在不断更新,一些类和方法可能会改变,尤为是内部。请始终参考GitHub上的最新代码。对于MvcSandbox ConfigureServices我已做更新:ide
public void ConfigureServices(IServiceCollection services) { services.AddMvcCore(); }
AddMvcCore是IServiceCollection的扩展方法。一般把一组关联的服务注册到服务集合(services collection)时都使用扩展方法这种模式。在构建MVC应用时,有两个扩展方法用来注册MVC服务(MVC Services),AddMvcCore是其中之一。相对AddMvc方法,AddMvcCore提供较少的服务子集。一些不须要使用MVC全部特性的简单程序,就可使用AddMvcCore。好比在构建REST APIs,就不须要Razor相关组件,我通常使用AddMvcCore。你也能够在AddMvcCore以后手动添加额外的服务,或者直接使用功能更多的AddMvc。AddMvcCore的实现:ui
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } var partManager = GetApplicationPartManager(services); services.TryAddSingleton(partManager); ConfigureDefaultFeatureProviders(partManager); ConfigureDefaultServices(services); AddMvcCoreServices(services); var builder = new MvcCoreBuilder(services, partManager); return builder; }
AddMvcCore作的第一件事就是经过GetApplicationPartManager静态方法得到ApplicationPartManager,把IServiceCollection做为参数传递给GetApplicationPartManager。
GetApplicationPartManager的实现:this
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; }
本方法首先检查当前已注册的服务中是否有ApplicationPartManager,一般状况是没有的,但极少状况你可能在调用AddMvcCore以前注册了其它服务。ApplicationPartManager若是不存在则建立一个新的。atom
接下来的代码是计算ApplicationPartManager的ApplicationParts属性值。首先从服务集合(services collection)中得到IHostingEnvironment。若是可以获IHostingEnvironment实例,则经过它得到程序名或封装名(application/assem bly name),而后传递给静态方法DefaultAssemblyPartDiscoveryProvider,DiscoverAssemblyParts方法将返回IEnumerable<ApplicationPart>。spa
public static IEnumerable<ApplicationPart> DiscoverAssemblyParts(string entryPointAssemblyName) { var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); var context = DependencyContext.Load(Assembly.Load(new AssemblyName(entryPointAssemblyName))); return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p)); }
DiscoverAssemblyParts 首先经过封装名(assembly name)得到封装对象(Assembly Object)和DependencyContex。本例封装名为“MvcSandbox”。而后将这些值传递给GetCandidateAssemblies方法,GetCandidateAssemblies再调用GetCandidateLibraries方法。调试
internal static IEnumerable<Assembly> GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext) { if (dependencyContext == null) { // Use the entry assembly as the sole candidate. return new[] { entryAssembly }; } return GetCandidateLibraries(dependencyContext) .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext)) .Select(Assembly.Load); } internal static IEnumerable<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext) { if (ReferenceAssemblies == null) { return Enumerable.Empty<RuntimeLibrary>(); } var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies); return candidatesResolver.GetCandidates(); }
解释一下GetCandidateLibraries作了什么:
它返回一个在<see cref=”ReferenceAssemblies”/>中引用的程序集列表,默认是咱们引用的主要MVC程序集,不包含项目自己的程序集。
更明确一些,得到的程序集列表包含了咱们的解决方案中引用的全部MVC程序集。
ReferenceAssemblies是一个定义在DefaultAssemblyPartDiscoveryProvider类中静态HashSet<string>,它包含13个MVC默认的程序集。
internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.ApiExplorer", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc.Cors", "Microsoft.AspNetCore.Mvc.DataAnnotations", "Microsoft.AspNetCore.Mvc.Formatters.Json", "Microsoft.AspNetCore.Mvc.Formatters.Xml", "Microsoft.AspNetCore.Mvc.Localization", "Microsoft.AspNetCore.Mvc.Razor", "Microsoft.AspNetCore.Mvc.Razor.Host", "Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.ViewFeatures" };
GetCandidateLibraries使用CandidateResolver类定位并返回“候选人”。CandidateResolver 经过运行时对象(RuntimeLibrary objects)和ReferenceAssemblies构造。每一个运行时对象依次迭代并添加到一个字典中,添加过程当中检查依赖名(dependency name)是否惟一,若是不惟一则抛出异常。
public CandidateResolver(IReadOnlyList<RuntimeLibrary> dependencies, ISet<string> referenceAssemblies) { var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase); foreach (var dependency in dependencies) { if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name)) { throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name)); } dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies)); } _dependencies = dependenciesWithNoDuplicates; }
每一个依赖对象(既RuntimeLibrary)都做为新的依赖对象存储到字典中。这些对象包含一个DependencyClassification属性,用来筛选须要的libraries (candidates)。DependencyClassification是一个枚举类型:
private enum DependencyClassification { Unknown = 0, Candidate = 1, NotCandidate = 2, MvcReference = 3 }
建立Dependency的时候,若是与ReferenceAssemblies HashSet匹配,则标记为MvcReference,其他标记为Unknown。
private Dependency CreateDependency(RuntimeLibrary library, ISet<string> referenceAssemblies) { var classification = DependencyClassification.Unknown; if (referenceAssemblies.Contains(library.Name)) { classification = DependencyClassification.MvcReference; } return new Dependency(library, classification); }
当CandidateResolver.GetCandidates方法被调用时,结合ComputeClassification方法,遍历整个依赖对象树。每一个依赖对象都将检查他的全部子项,直到匹配Candidate或者MvcReference,同时标记父依赖项为Candidate类型。遍历结束将返回包含identified candidates的IEnumerable<RuntimeLibrary>。例如本例,只有MvcSandbox程序集被标记为Candidate。
public IEnumerable<RuntimeLibrary> GetCandidates() { foreach (var dependency in _dependencies) { if (ComputeClassification(dependency.Key) == DependencyClassification.Candidate) { yield return dependency.Value.Library; } } } private DependencyClassification ComputeClassification(string dependency) { Debug.Assert(_dependencies.ContainsKey(dependency)); var candidateEntry = _dependencies[dependency]; if (candidateEntry.Classification != DependencyClassification.Unknown) { return candidateEntry.Classification; } else { var classification = DependencyClassification.NotCandidate; foreach (var candidateDependency in candidateEntry.Library.Dependencies) { var dependencyClassification = ComputeClassification(candidateDependency.Name); if (dependencyClassification == DependencyClassification.Candidate || dependencyClassification == DependencyClassification.MvcReference) { classification = DependencyClassification.Candidate; break; } } candidateEntry.Classification = classification; return classification; } }
DiscoverAssemblyParts将返回的candidates转换为新的AssemblyPart。此对象是对程序集的简单封装,只包含了如名称、类型等主要封装属性。后续我可能单独撰文分析此类。
最后,经过GetApplicationPartManager,AssemblyParts被加入到ApplicationPartManager。
var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName); foreach (var part in parts) { manager.ApplicationParts.Add(part); } } return manager;
返回的ApplicationPartManager实例经过AddMvcCore扩展方法加入到services collection。
接下来AddMvcCore把ApplicationPartManager做为参数调用静态ConfigureDefaultFeatureProviders方法,为ApplicationPartManager的FeatureProviders添加ControllerFeatureProvider。
private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager) { if (!manager.FeatureProviders.OfType<ControllerFeatureProvider>().Any()) { manager.FeatureProviders.Add(new ControllerFeatureProvider()); } }
ControllerFeatureProvider将被用在ApplicationPar的实例中发现controllers。我将在后续的博文中介绍ControllerFeatureProvider。如今咱们继续研究AddMovCore的最后一步。(ApplicationPartManager此时已更新)
首先调用私有方法ConfigureDefaultServices,经过Microsoft.AspNetCore.Routing提供的AddRouting扩展方法开启routing功能。它提供启用routing功能所需的必要服务和配置。本文不对此做详细描述。
AddMvcCore接下来调用另外一个私有方法AddMvcCoreServices,该方法负责注册MVC核心服务,包括框架可选项,action 的发现、选择和调用,controller工厂,模型绑定和认证。
最后AddMvcCore经过services collection和ApplicationPartManager,构造一个新的MvcCoreBuilder对象。此类被叫作:
容许细粒度的配置MVC基础服务(Allows fine grained configuration of essential MVC services.)
AddMvcCore扩展方法返回MvcCoreBuilder,MvcCoreBuilder包含IserviceCollection和ApplicationPartManager属性。MvcCoreBuilder和其扩展方法用在AddMvcCore初始化以后作一些额外的配置。实际上,AddMvc方法中首先调用的是AddMvcCore,而后使用MvcCoreBuilder配置额外的服务。
要把上述全部问题都解释清楚不是件容易的事,因此简单总结一下。本文咱们分析的是MVC底层代码,主要实现了把MVCs须要的服务添加到IserviceCollection中。经过追踪ApplicationPartManager的建立过程,咱们了解了MVC如何一步步建立内部应用模型(ApplicationModel)。虽然咱们并无看到不少实际的功能,但经过对startup的跟踪分析咱们发现了许多有趣的东西,这为后续分析奠基了基础。
以上就是我对AddMvcCore的初步探究,共有46个附加项注册到IservceCollection中。下一篇我将进一步分析AddMvc扩展方法。