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

来源:http://www.cnblogs.com/lookerblue/archive/2017/10/31/7760355.html
-Advertisement-
Play Games

原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc 發佈於:2017年4月環境:ASP.NET Core 1.1 本系列前面兩篇文章介紹了ASP.NET Core中IServiceCollection兩個主要擴展方 ...


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

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

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

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

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方法:

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會創建並返回自身的新實例副本。

再下來,UserMvc初始化一個新的RouteBuilder,同時設定預設處理程式(default handler)為已註冊的MvcRouteHandler。此時DI就像有魔力,一堆依賴對象開始實例化。這裡請求MvcRouteHandler是因為其構造函數中有一些依賴關係,所以相關的其它類也會被初始化。每個這些類的構造函數都會請求額外的依賴關係,然後創建它們。這是DI系統工作的一個完美例子。雖然在ConfigureServices中所有的介面和具體實現已在container中註冊,但實際對象只會在被註入時創建。通過創建RouteBuilder,對象創建就像滾雪球,直到所有依賴關係都被構造。一些對象作為單例模式註冊,無論ServiceProvider是否請求都會創建,其生命周期貫穿整個應用程式。其它對象可能是在每次請求時通過構造函數創建或者其它情況,每次創建的對象都是特有的。瞭解依賴註入是如何工作的,特別是ASP.NET Core ServiceProvider的詳細信息超出了這篇文章的範圍。

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());

}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇不是專業權威的tmux指南,只是我的一點點使用經驗,更詳盡的資料請google。 tmux 是什麼 先說說tmux是什麼,不要被什麼視窗會話管理嚇到了,工具都是用來解決問題的。 在遠程linux機器開發是每個程式員工作中最常見的場景,不可避免地會遇到問題:一個視窗不夠用!最初我在windows系 ...
  • 在高可用(HA)系統中,當聯繫2個節點的“心跳線”斷開時,本來為一整體、動作協調的HA系統,就分裂成為2個獨立的個體。由於相互失去了聯繫,都以為是對方出了故障。兩個節點上的HA軟體像“裂腦人”一樣,爭搶“共用資源”、爭起“應用服務”,就會發生嚴重後果——或者共用資源被瓜分、2邊“服務”都起不來了;或 ...
  • 1>監控概述 通常運維人員在一個企業當中所需要管理一臺或者多台伺服器,或者甚至更多,特別是BAT公司或者門戶級別的公司,一個人管理的伺服器可能上百甚至上千台 而在這些管理的過程當中,作為運維人員我們需要知道我所管理的每一個伺服器所運行的詳細狀態,其中包括,物理資源的消耗狀態(CPU 記憶體 硬碟 IO ...
  • U盤啟動。進入到此界面後 按Tab鍵,將命令修改為:>vmlinuz initrd=initrd.img linux dd quiet 改好之後回車,然後就會列出你的設備列表了,找到你的U盤所在的盤符。並且記住了,後面要用 再次通過U盤啟動電腦,繼續修改第2步中出現的命令 重覆第2步,然後將底下的命 ...
  • MSYS2下載:http://www.msys2.org/ MSYS2是什麼 MSYS2 (Minimal SYStem 2) 是一個MSYS的獨立改寫版本,主要用於 shell 命令行開發環境。同時它也是一個在Cygwin (POSIX 相容性層) 和 MinGW w64(從"MinGW 生成") ...
  • 一、服務分類 1、二進位包 (1)、 快速安裝、卸載、升級和管理軟體 (2)、安裝簡潔、速度快 (3)、經過封裝,無法直接獲取源代碼 (4)、功能選擇性差,功能定製不靈活 2、源碼包 (1)、獲得最新的軟體版本,及時修複bug (2)、根據用戶需要,靈活定製軟體功能 二、RPM包的服務管理 RPM包 ...
  • 安裝: tar -zxvf keepalived-1.2.2.tar.gz yum list all |grep "ipvsadm" yum -y install kernel-devel openssl-develpopt-devel ipvsadm libnl libnl-devel servi ...
  • 演示產品近乎下載地址:http://www.jinhusns.com/Products/Download ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...