剖析ASP.NET Core(Part 3)- UseMvc(譯)

原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc
發佈於:2017年4月
環境:ASP.NET Core 1.1json

本系列前面兩篇文章介紹了ASP.NET Core中IServiceCollection兩個主要擴展方法(AddMvcCore與AddMvc)。當你準備在程序中使用MVC中間件時,它們用來添加所需的MVC服務。數組

接下來,要在ASP.NET Core程序中啓動MVC還須要在Startup類的Configure方法中執行UseMvc IApplicationBuilder擴展方法。該方法註冊MVC中間件到應用程序管道中,以便MVC框架可以處理請求並返回響應(一般是view result或json)。本文我將分析一下在應用程序啓動時UserMvc方法作了什麼。mvc

和先前文章同樣,我使用rel/1.1.2 MVC版本庫做爲分析對象。代碼是基於原來的project.json,由於在VS2017中彷佛沒有簡單辦法實現調試多個ASP.NET Core源代碼。app

UseMvc是IApplicationBuilder的一個擴展方法,帶有一個Action<IRouteBuilder>委託參數。IRouteBuilder將被用於配置MVC的路由。UserMvc還有一個重載方法,不須要任何參數,它只是簡單的傳遞一個空委託調用主函數。以下:框架

public static IApplicationBuilder UseMvc(this IApplicationBuilder app)
{
    if (app == null)
    {
        throw new ArgumentNullException(nameof(app));
    }

    return app.UseMvc(routes =>
    {
    });
}

主UseMvc方法:ide

public static IApplicationBuilder UseMvc(
    this IApplicationBuilder app,
    Action<IRouteBuilder> configureRoutes)
{
    if (app == null)
    {
        throw new ArgumentNullException(nameof(app));
    }

    if (configureRoutes == null)
    {
        throw new ArgumentNullException(nameof(configureRoutes));
    }

    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null)
    {
        throw new InvalidOperationException(Resources.FormatUnableToFindServices(
            nameof(IServiceCollection),
            "AddMvc",
            "ConfigureServices(...)"));
    }

    var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>();
    middlewarePipelineBuilder.ApplicationBuilder = app.New();

    var routes = new RouteBuilder(app)
    {
        DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
    };

    configureRoutes(routes);

    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

咱們來分析一下此方法。首先檢查IServiceProvider中是否有MvcMarkerService服務註冊。爲此使用ApplicationServices屬性暴露IServiceProvider,再調用GetService,嘗試查找已註冊的MvcMarkerServiceMvcMarkerServiceAddMvcCore執行時已被註冊,所以若是沒有找到,則代表在ConfigureServices執行前沒有調用過AddMvcAddMvcCore。這樣的Marker Services在不少地方使用,它能夠幫助在代碼執行以前,檢查是否存已存在正確的依賴關係。函數

接下來,UserMvcServiceProvider請求一個MiddlewareFilterBuilder,並使用IApplicationBuilder.New()方法設定其ApplicationBuilder。當調用此方法時,ApplicationBuilder會建立並返回自身的新實例副本。ui

再下來,UserMvc初始化一個新的RouteBuilder,同時設定默認處理程序(default handler)爲已註冊的MvcRouteHandler。此時DI就像有魔力,一堆依賴對象開始實例化。這裏請求MvcRouteHandler是由於其構造函數中有一些依賴關係,因此相關的其它類也會被初始化。每一個這些類的構造函數都會請求額外的依賴關係,而後建立它們。這是DI系統工做的一個完美例子。雖然在ConfigureServices中全部的接口和具體實現已在container中註冊,但實際對象只會在被注入時建立。經過建立RouteBuilder,對象建立就像滾雪球,直到全部依賴關係都被構造。一些對象做爲單例模式註冊,不管ServiceProvider是否請求都會建立,其生命週期貫穿整個應用程序。其它對象多是在每次請求時經過構造函數建立或者其它狀況,每次建立的對象都是特有的。瞭解依賴注入是如何工做的,特別是ASP.NET Core ServiceProvider的詳細信息超出了這篇文章的範圍。this

RouteBuilder建立以後,Action<IRouteBuilder>委託被調用,使用RouteBuilder做爲惟一參數。在MvcSandbox示列中,咱們稱之爲UseMvc方法,經過一個lambda傳遞給代理方法。此功能將映射一個名爲「default」的路由,以下所示:編碼

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

MapRouteIrouteBuilder的一個擴展方法,主要的MapRoute方法以下所示:

public static IRouteBuilder MapRoute(
    this IRouteBuilder routeBuilder,
    string name,
    string template,
    object defaults,
    object constraints,
    object dataTokens)
{
    if (routeBuilder.DefaultHandler == null)
    {
        throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
    }

    var inlineConstraintResolver = routeBuilder
        .ServiceProvider
        .GetRequiredService<IInlineConstraintResolver>();

    routeBuilder.Routes.Add(new Route(
        routeBuilder.DefaultHandler,
        name,
        template,
        new RouteValueDictionary(defaults),
        new RouteValueDictionary(constraints),
        new RouteValueDictionary(dataTokens),
        inlineConstraintResolver));

    return routeBuilder;
}

這將請求一個由ServiceProvider解析的IinlineConstraintResolver實例給DefaultInlineConstraintResolver。該類使用IOptions <RouteOptions>參數初始化構造函數。

Microsoft.Extensions.Options程序集調用RouteOptions做爲參數的MvcCoreRouteOptionsSetup.Configure方法。這將添加一個KnownRouteValueConstraint類型的約束映射到約束映射字典。當首次構建RouteOptions時,該字典將初始化多個默認約束。我會在之後的文章中詳細介紹路由代碼。

經過提供的名稱和模板構建一個新的Route對象。在咱們的例子中,新對象添到了RouteBuilder.Routes列表中。至此,在MvcSandbox示列程序運行時,咱們的router builder就有了一條路由記錄。

請注意,MvcApplicationBuilderExtensions類還包括一個名爲UseMvcWithDefaultRoute的擴展方法。此方法會調用UseMvc,經過硬編碼設定默認路由。

public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app)
{
    if (app == null)
    {
        throw new ArgumentNullException(nameof(app));
    }

    return app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

該路由使用與MvcSandbox程序中定義的相同名稱和模板進行定義。所以,它可能被用做MvcSandbox Startup類中的輕微代碼保護程序(slight code saver)。對於最基本的Mvc程序,使用該擴展方法可能足以知足路由的需求。但在大多實際使用場景中,我相信你會傳遞一套更加完整的路由。這是一個很好的方式(shorthand),若是你想從基本路由模板開始,就能夠直接調用UseMvc()而不使用任何參數。

接下來,調用靜態AttributeRouting.CreateAttributeMegaRoute方法,同時把生成的路由添加到RouteBuilder中的Routes List(索引位置爲0)。CreateAttributeMegaRoute已在「Creates an attribute route using the provided services and provided target router」文中講述,它看起來像這樣:

public static IRouter CreateAttributeMegaRoute(IServiceProvider services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    return new AttributeRoute(
        services.GetRequiredService<IActionDescriptorCollectionProvider>(),
        services,
        actions => 
        {
            var handler = services.GetRequiredService<MvcAttributeRouteHandler>();
            handler.Actions = actions;
            return handler;
        });
}

該方法建立了一個實現了IRoute接口的新AttributeRoute。其構造函數看起來想象這樣:

public AttributeRoute(
    IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
    IServiceProvider services,
    Func<ActionDescriptor[], IRouter> handlerFactory)
{
    if (actionDescriptorCollectionProvider == null)
    {
        throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider));
    }

    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    if (handlerFactory == null)
    {
        _handlerFactory = handlerFactory;
    }

    _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
    _services = services;
    _handlerFactory = handlerFactory;
}

該構造函數須要一個IactionDescriptorCollectionProvider,一個IServiceProvider和一個接受ActionDescriptor數組參數,並返回IRouterFunc。默認狀況下,經過AddMvc註冊服務時將得到一個ActionDescriptorCollectionProvider實例,它是一個經過ServiceProvider註冊的單例(singleton)對象。ActionDescriptors表示在應用程序中建立和發現可用的MVC actions。這些對象咱們另文分析。

建立新的AttributeRoute代碼(CreateAttributeMegaRoute方法內部)使用lambda來定義Func <ActionDescriptor []IRouter>的代碼。在這種狀況下,委託函數從ServiceProvider請求一個MvcAttributeRouteHandler。由於被註冊爲transient,因此每次請求ServiceProvider將返回一個新的MvcAttributeRouteHandler實例。委託代碼而後使用傳入的ActionDescriptions數組在MvcAttributeRouteHandler上設置Actions屬性(ActionDescriptions的數組),最後返回新的處理程序。

返回UserMvcIapplicationBuilder中的UseRouter擴展方法完成調用。傳遞給UseRouter的對象是經過調用RouteBuilderBuild方法來建立的。該方法將添加路由到路由集合。RouteCollection內部將追蹤哪些是已命名,哪些是未命名。在咱們的MvcSandbox示例中,咱們會獲得一個命名爲「default」的路由和一個未命名的AttributeRoute

UseRouter有兩個簽名。此時咱們傳遞一個IRouter,因此調用如下方法:

public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
    if (builder == null)
    {
        throw new ArgumentNullException(nameof(builder));
    }

    if (router == null)
    {
        throw new ArgumentNullException(nameof(router));
    }

    if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
    {
        throw new InvalidOperationException(Resources.FormatUnableToFindServices(
            nameof(IServiceCollection),
            nameof(RoutingServiceCollectionExtensions.AddRouting),
            "ConfigureServices(...)"));
    }

    return builder.UseMiddleware<RouterMiddleware>(router);
}

該方法實現檢查ServiceProvider中的RoutingMarkerService。假設這是按預期註冊的,它將RouterMiddleware添加到中間件管道(middeware pipeline)中。正是這個中間件,使用IRouter來嘗試將控制器的請求和MVC中的動做(action)進行匹配,以便處理它們。這個過程的細節將在將來的博文中展示。

到此,應用程序管道已配置,應用程序已準備好接收請求。本文也能夠結束了。

小結

本文咱們分析了UseMvc而且看到它設置了將在稍後使用的MiddlewareFilterBuilder。而後它還經過RouteBuilder得到一個IRouter,在這個階段的大部分工做都是註冊路由。一旦設置完成,路由中間件就被註冊到管道中。這個代碼(中間件)知道如何檢查傳入的請求,並將路徑映射到適當的控制器和能夠處理它們的操做。

configureRoutes(routes);

 

    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));

 

    return app.UseRouter(routes.Build());

}

相關文章
相關標籤/搜索