剖析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”對本系列來說更加貼切。

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

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

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添加TagHelpersRazor 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到底做了些什么。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM