原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore
發佈於:2017年3月
環境:ASP.NET Core 1.1安全
歡迎閱讀剖析ASP.NET Core源代碼系列第二部分。細心的讀者可能發現文章標題發生了變化,去掉了「MVC」。雖然我最感興趣的是MVC的實現,但隨着剖析的深刻,不可避免的會涉及到ASP.NET Core 核心框架的內容,好比 IRouter。所以,適當擴大研究範圍是必要的,「剖析ASP.NET Core」對本系列來講更加貼切。mvc
在Part 1,咱們瞭解了AddMvcCore擴展方法,本文咱們將分析AddMvc擴展方法。咱們繼續使用rel/1.1.2版本的ASP.NET Core MVC。將來代碼可能會發生變化,請使用相同版本。咱們的起點還是MvcSandbox項目,Startup.cs中的ConfigureServices方法以下:app
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); }
AddMvc擴展方法的實現:框架
public static IMvcBuilder AddMvc(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } var builder = services.AddMvcCore(); builder.AddApiExplorer(); builder.AddAuthorization(); AddDefaultFrameworkParts(builder.PartManager); // Order added affects options setup order // Default framework order builder.AddFormatterMappings(); builder.AddViews(); builder.AddRazorViewEngine(); builder.AddCacheTagHelper(); // +1 order builder.AddDataAnnotations(); // +1 order // +10 order builder.AddJsonFormatters(); builder.AddCors(); return new MvcBuilder(builder.Services, builder.PartManager); }
上面代碼首先調用咱們在Part 1中分析過的AddMvcCore擴展方法,執行相同的配置和服務註冊,包括建立了一個ApplicationPartManager。AddMvcCore的返回類型是一個MvcCoreBuilder,AddMvc使用一個變量保存(builder)。正如我在Part 1中提到的,它(builder)提供了對AddMvcCore生成的IServiceCollection和ApplicationPartManager的訪問。ide
AddMvc調用AddApiExplorer擴展方法,向builder的IServiceCollection添加兩個服務註冊:ApiDescriptionGroupCollectionProvider和DefaultApiDescriptionProvider。ui
public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } AddApiExplorerServices(builder.Services); return builder; } // Internal for testing. internal static void AddApiExplorerServices(IServiceCollection services) { services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>(); services.TryAddEnumerable( ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>()); }
接下來調用AddAuthorization擴展方法:this
public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder) { AddAuthorizationServices(builder.Services); return builder; } internal static void AddAuthorizationServices(IServiceCollection services) { services.AddAuthorization(); services.TryAddEnumerable( ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>()); }
首先它調用了Microsoft.AspNetCore.Authorization程序集中的AddAuthorization擴展方法。這裏不深刻介紹,但它添加了啓用受權(authorization)的核心服務。以後,AuthorizationApplicationModelProvider被添加到ServicesCollection中。atom
接下來返回AddMvc主方法,它調用靜態私有方法AddDefaultFrameworkParts添加TagHelpers和Razor AssemblyParts。spa
private static void AddDefaultFrameworkParts(ApplicationPartManager partManager) { var mvcTagHelpersAssembly = typeof(InputTagHelper).GetTypeInfo().Assembly; if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcTagHelpersAssembly)) { partManager.ApplicationParts.Add(new AssemblyPart(mvcTagHelpersAssembly)); } var mvcRazorAssembly = typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly; if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcRazorAssembly)) { partManager.ApplicationParts.Add(new AssemblyPart(mvcRazorAssembly)); } }
本方法首先得到Microsoft.AspNetCore.Mvc.TagHelpers中的InputTagHelper類的封裝(Assembly),而後檢查ApplicationPartManager中的ApplicationParts列表是否包含匹配的程序集,若是不存在則添加。而後使用一樣方法處理Razor,使用的是Microsoft.AspNetCore.Mvc.Razor中的UrlResolutionTagHelper,一樣執行若是不存在則添加操做。設計
再次返回AddMvc主方法,更多的項目經過擴展方法添加到ServiceCollection中。AddMvc調用AddFormatterMappings註冊FormatFilter類。
接下來調用AddViews。首先使用AddDataAnnotations擴展方法添加DataAnnotations服務。接下來把ViewComponentFeatureProvider添加到ApplicationPartManager.FeatureProviders。最後註冊了一些基於視圖的服務,如singletons。這個類很是大,這裏我不貼出全部代碼。關鍵部分以下:
public static IMvcCoreBuilder AddViews(this IMvcCoreBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.AddDataAnnotations(); AddViewComponentApplicationPartsProviders(builder.PartManager); AddViewServices(builder.Services); return builder; } private static void AddViewComponentApplicationPartsProviders(ApplicationPartManager manager) { if (!manager.FeatureProviders.OfType<ViewComponentFeatureProvider>().Any()) { manager.FeatureProviders.Add(new ViewComponentFeatureProvider()); } }
接下來AddMvc調用AddRazorViewEngine擴展方法:
public static IMvcCoreBuilder AddRazorViewEngine(this IMvcCoreBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.AddViews(); // AddMvc主方法中執行過 AddViews() AddRazorViewEngineFeatureProviders(builder); AddRazorViewEngineServices(builder.Services); return builder; } private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder) { if (!builder.PartManager.FeatureProviders.OfType<TagHelperFeatureProvider>().Any()) { builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider()); } if (!builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>().Any()) { builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); } if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any()) { builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider()); } }
這裏再次調用AddViews引發了個人好奇。在AddRazorViewEngine以前調用AddViews彷佛有些多餘,由於AddRazorViewEngine會爲咱們註冊全部服務。這樣操做雖然是安全的,由於這些擴展方法都使用 TryAdd… 形式,可以避免服務的重複註冊,但看到這樣的重複調用仍是以爲有些奇怪。爲知足個人好奇,我在Mvc GitHub上開啓了一個問題,求證是一個錯誤仍是有意設計。我獲得了微軟Ryan Nowak很是迅速的回覆,確認這是一個設計決定,使依賴關係更加明確和容易看出。
AddRazorViewEngine 添加了三個feature providers到ApplicationPartManager.FeatureProviders:TagHelperFeatureProvider,MetadataReferenceFeatureProvider和ViewsFeatureProvider。這些是實現razor視圖功能必要的服務。
AdddMvc而後調用另外一個擴展方法來爲Cache Tag Helper功能添加服務。這是一個很是簡單的擴展方法,僅註冊了5個所需的服務。而後返回AddMvc,再次調用AddDataAnnotations。AddViews此前已調用過此方法,這與前面提到的設計決定相同。
AddMvc而後調用AddJsonFormatters擴展方法,將幾個項目添加到ServicesCollection。
最後被調用擴展方法是Microsoft.AspNetCore.Cors中的AddCors,添加Cors相關的服務。
隨着服務註冊的完成,AddMvc建立了一個新的MvcBuilder,將當前的ServicesCollection和ApplicationPartManager做爲屬性存儲。就像咱們在第一篇文章中看到的MvcBuilder同樣,MvcBuilder被描述爲「容許細粒度的配置MVC服務」(allowing fine grained configurations of MVC services)。
AddMvc執行完成後,services collection共有148個註冊服務,比AddMvcCore方法多了86個服務。ApplicationPartManager中有3個ApplicationParts和5個FeatureProviders。
以上就是我對AddMvc的分析。相比前面對ApplicationPartManager的鋪墊、建立分析,顯得不那麼讓人興奮,咱們主要是調用擴展方法擴展服務。如你所見,許多服務是針對使用Views的Web 應用程序。若是是單純的API應用程序,你可能不須要這些服務。在我開發過的API項目中,就使用AddMvcCore,同時經過builder的擴展方法添加額外幾個咱們須要的服務。下面是我在實際運用中的示例代碼:
services.AddMvcCore(options => { options.UseHtmlEncodeModelBinding(); }) .AddJsonFormatters(options => { options.ContractResolver = new CamelCasePropertyNamesContractResolver(); }) .AddApiExplorer() .AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireAssertion(ctx => ClaimsHelper.IsAdminUser(ctx.User.Claims.ToList())) .Build(); options.AddPolicy(SecurityPolicyNames.DoesNotRequireAdminUser, policy => policy.RequireAuthenticatedUser()); });
下篇文章我將講解Startup.cs中的Configure方法調用的UseMvc到底作了些什麼。