原文: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擴展方法。