剖析ASP.NET Core(Part 2)- AddMvc(譯)

原文: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擴展方法,執行相同的配置和服務註冊,包括建立了一個ApplicationPartManagerAddMvcCore的返回類型是一個MvcCoreBuilderAddMvc使用一個變量保存(builder)。正如我在Part 1中提到的,它(builder)提供了對AddMvcCore生成的IServiceCollectionApplicationPartManager的訪問。ide

AddMvc調用AddApiExplorer擴展方法,向builderIServiceCollection添加兩個服務註冊:ApiDescriptionGroupCollectionProviderDefaultApiDescriptionProviderui

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添加TagHelpersRazor AssemblyPartsspa

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到底作了些什麼。

相關文章
相關標籤/搜索