原文: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”對本系列來說更加貼切。
在Part 1,我們了解了AddMvcCore擴展方法,本文我們將分析AddMvc擴展方法。我們繼續使用rel/1.1.2版本的ASP.NET Core MVC。未來代碼可能會發生變化,請使用相同版本。我們的起點仍是MvcSandbox項目,Startup.cs中的ConfigureServices方法如下:
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的訪問。
AddMvc調用AddApiExplorer擴展方法,向builder的IServiceCollection添加兩個服務注冊:ApiDescriptionGroupCollectionProvider和DefaultApiDescriptionProvider。
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擴展方法:
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中。
接下來返回AddMvc主方法,它調用靜態私有方法AddDefaultFrameworkParts添加TagHelpers和Razor AssemblyParts。
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到底做了些什么。