了解ASP.NET Core端點路由


原作者Areg Sarkissian

 

介紹

在這篇文章中,我將說明從版本2.2開始已添加到ASP.NET Core中間件管道中的新的端點路由功能,以及它如何演進到當前在預覽版3的即將發布的版本3.0。

端點路由背后的動機

在端點路由之前,在HTTP請求處理管道的末尾,在ASP.NET Core MVC中間件中完成了ASP.NET Core應用程序的路由解析。這意味着在中間件管道中的MVC中間件之前,路由信息(例如將執行哪些控制器操作)對於處理請求的中間件不可用。

例如在CORS或授權中間件中提供此路由信息特別有用,以將該信息用作授權過程中的一個因素。

端點路由還允許我們將路由匹配邏輯與MVC中間件解耦,然​​后將其移動到其自己的中間件中。它允許MVC中間件專注於其將請求分發到由端點路由中間件解決的特定控制器操作方法的責任。

新的端點路由中間件

由於上述原因,端點路由功能的誕生是為了使路由解析能夠在單獨的端點路由中間件中的管道中更早地發生。可以在管道中的任何位置放置此新的中間件,之后管道中的其他中間件可以訪問已解析的路由數據。

端點路由中間件API隨即將發布的.NET Core框架3.0版本一起發展。因此,我將在以下各節中描述的API可能不是該功能的最終版本。但是,總體概念和對如何使用端點路由進行路由解析和調度的理解仍然應該適用。

在以下各節中,我將引導您完成從2.2版到3.0版Preview 3的端點路由實現的當前迭代,然后我將注意到基於當前ASP.NET Core源代碼的一些更改。

端點路由涉及的三個核心概念

您需要了解三個總體概念,才能理解端點路由的工作方式。

這些是以下內容:

  • 端點路由解析
  • 端點派遣
  • 端點路由映射

端點路由解析

端點路由解析是查看傳入請求並將請求使用路由映射映射到端點的概念。端點表示傳入請求解析的控制器操作,以及附加到與請求匹配的路由的其他元數據。

路由解析中間件的工作是使用基於路由映射解析的路由中的路由信息來構造Endpoint對象。然后,中間件將該對象放置在http上下文中,在該上下文中,在管道中的端點路由中間件可以訪問端點對象並使用其中的路由信息​​之后出現的其他中間件。

在端點路由之前,路由解析是在中間件管道末端的MVC中間件中完成的。該框架的當前2.2版本添加了一個新的端點路由解析中間件,該中間件可以放置在管道中的任何位置,但是將端點分發保留在MVC中間件中。這將在3.0版本中發生變化,在該版本中,終結點調度將在單獨的終結點調度中間件中進行,該中間件將替換MVC中間件。

端點派遣

端點調度是調用控制器操作方法的過程,該方法對應於由端點路由中間件解析的端點。

端點分派中間件是管道中的最后一個中間件,它從http上下文中獲取端點對象,並分派給解析的端點指定的特定控制器操作。

當前,在2.2版中,在管道末端的MVC中間件中完成對action方法的調度。

在3.0版預覽3中,刪除了MVC中間件。相反,默認情況下,端點調度發生在中間件管道的末尾。由於已刪除MVC中間件,因此通常傳遞給MVC中間件的路由映射配置將傳遞給端點路由解析中間件。

根據當前的源代碼,即將發布的3.0最終版本應該在管道的末尾放置一個新的端點路由中間件,以使端點再次顯式分派。路由映射配置將傳遞到此新的中間件,而不是版本3預覽版3中的端點路由解析中間件。

端點路由映射

定義路由中間件時,我們可以選擇傳入一個lambda函數,該函數包含的路由映射將覆蓋ASP.NET Core MVC中間件擴展方法指定的默認路由映射。

路由解析過程使用路由映射將傳入的請求參數與路由映射中指定的路由進行匹配。

使用新的端點路由功能,ASP.NET Core團隊必須決定應使用哪個中間件(端點解析或端點調度中間件)獲取路由映射配置lambda作為參數。

實際上,這是API不斷變化的端點路由的一部分。在撰寫本文時,路由映射已從路由解析中間件移至端點調度程序中間件。

我將首先在版本3預覽3中向您展示路由映射API,然后在ASP.NET Core源代碼中向您展示最新的路由映射API。在源代碼版本中,我們將看到路由映射已移至端點調度程序中間件擴展方法。

重要的是要注意,在應用程序啟動配置期間設置路由映射之后,端點解析會在運行時請求處理期間發生。因此,路由解析中間件可以在請求處理期間訪問路由映射,而不管路由映射配置將傳遞到哪個中間件。

訪問已解析的端點

端點路由解析中間件之后的任何中間件都將能夠通過HttpContext訪問已解析的端點。

以下代碼段顯示了如何在自己的中間件中完成此操作:

//our custom middleware app.Use((context, next) => { var endpointFeature = context.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)] as Microsoft.AspNetCore.Http.Features.IEndpointFeature; Microsoft.AspNetCore.Http.Endpoint endpoint = endpointFeature?.Endpoint; //Note: endpoint will be null, if there was no //route match found for the request by the endpoint route resolver middleware if (endpoint != null) { var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern ?.RawText; Console.WriteLine("Name: " + endpoint.DisplayName); Console.WriteLine($"Route Pattern: {routePattern}"); Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata)); } return next(); }); 

如您所見,我正在通過IEndpointFeature或Http Context訪問已解析的終結點對象。該框架提供了包裝器方法來訪問終結點對象,而不必直接進入上下文,如我在此所示。

端點路由配置

中間件管道終結點路由解析器中間件,終結點調度程序中間件和終結點路由映射lambda是通過ASP.NET Core項目文件Startup.Configure方法設置的Startup.cs

此配置在2.2和3.0 Preview 3版本之間進行了更改,並且在3.0發布版本之前仍在更改。因此,為了演示端點路由配置,我將基於上面列出的三個核心概念將端點路由中間件配置的一般形式聲明為偽代碼:

//psuedocode that passes route map to endpoint resolver middleware public void Configure(IApplicationBuilder app , IHostingEnvironment env) { //middleware configured before the UseEndpointRouteResolverMiddleware middleware //that does not have access to the endpoint object app.UseBeforeEndpointResolutionMiddleware(); //middleware that inspects the incoming request, resolves a match to the route map //and stores the resolved endpoint object into the httpcontext app.UseEndpointRouteResolverMiddleware(routes => { //This is the route mapping configuration passed to the endpoint resolver middleware routes.MapControllers(); }) //middleware after configured after the UseEndpointRouteResolverMiddleware middleware //that can access to the endpoint object app.UseAfterEndpointResolutionMiddleware(); //The middleware at the end of the pipeline that dispatches the controler action method //will replace the current MVC middleware app.UseEndpointDispatcherMiddleware(); } 

此版本的偽代碼顯示了作為參數傳遞給UseEndpointRouteResolverMiddleware端點路由解析中間件擴展方法的路由映射lambda 

匹配當前源代碼的替代版本如下所示:

//psuedocode version 2 that passes route map to endpoint dispatch middleware public void Configure(IApplicationBuilder app , IHostingEnvironment env) { app.UseBeforeEndpointResolutionMiddleware() //This is the endpoint route resolver middleware app.UseEndpointRouteResolverMiddleware(); //This middleware can access the resolved endpoint object via HttpContext app.UseAfterEndpointResolutionMiddleware(); //This is the endpoint dispatch middleware app.UseEndpointDispatcherMiddleware(routes => { //This is the route mapping configuration passed to the endpoint dispatch middleware routes.MapControllers(); }); } 

在此版本中,路由映射配置作為參數傳遞給端點調度中間件擴展方法UseEndpointDispatcherMiddleware

無論哪種方式,UseEndpointRouteResolverMiddleware()端點解析程序中間件都可以在請求處理時訪問路由映射以進行路由匹配。

一旦路由與UseEndpointRouteResolverMiddlewareEndpoint對象匹配,便將使用route參數構造並設置為httpcontext,以便后續管道中的中間件可以訪問Endpoint對象並在需要時使用它。

在此偽代碼的版本3預覽3版本中,路由映射被傳遞到,UseEndpointRouteResolverMiddleware並且UseEndpointDispatcherMiddleware在管道的末尾不存在這是因為在此版本中,ASP.NET框架本身隱式在請求管道的末尾分派了已解析的終結點。

因此,表示版本3預覽版3的偽代碼具有以下形式:

//pseudo code representing v3 preview 3 endpoint routing API public void Configure(IApplicationBuilder app , IHostingEnvironment env) { app.UseBeforeEndpointResolutionMiddleware() //This is the endpoint route resolver middleware app.UseEndpointRouteResolverMiddleware(routes => { //The route mapping configuration is passed to the endpoint resolution middleware routes.MapControllers(); }) //This middleware can access the resolved endpoint object via HttpContext app.UseAfterEndpointResolutionMiddleware() // The resolved endpoint is implicitly dispatched here at the end of the pipeline // and so there is no explicit call to a UseEndpointDispatcherMiddleware } 

該API似乎隨着3.0發行版的變化而變化,因為當前的源代碼顯示,該API UseEndpointDispatcherMiddleware已重新添加,並且中間件將路由映射作為參數,如上面的第二版偽代碼所示。

2.2版中的端點路由

如果使用.NET Core SDK版本2.2創建Web API項目,則該Startup.Configure方法中將顯示以下代碼

public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); //By default endpoint routing is not added //the MVC middleware dispatches the controller action //and the MVC middleware configures the default route mapping app.UseMvc(); } 

使用UseMvc()擴展方法在中間件流水線的末尾配置MVC中間件此方法在啟動配置時在內部設置默認的MVC路由映射配置,並在請求處理期間調度控制器操作。

默認情況下,v2.2中的即用型模板僅配置MVC調度程序中間件。這樣,MVC中間件還根據路由映射配置和傳入的請求數據來處理路由解析。

但是,我們可以使用一些其他配置來添加“端點路由”,如下所示:


using Microsoft.AspNetCore.Internal; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); //added endpoint routing that will resolve the endpoint object app.UseEndpointRouting(); //middleware below will have access to the Endpoint app.UseHttpsRedirection(); //the MVC middleware dispatches the controller action //and the MVC middleware configures the default route mapping app.UseMvc(); } 

在這里,我們添加了命名空間Microsoft.AspNetCore.Internal包括它在內,可以啟用一種附加的IApplicationBuilder擴展方法UseEndpointRouting,該方法是解決路由並將端點對象添加到httpcontext的端點解析中間件。

您可以在以下位置查看UseEndpointRouting版本2.2中擴展方法的源代碼

https://github.com/aspnet/AspNetCore/blob/v2.2.4/src/Http/Routing/src/Internal/EndpointRoutingApplicationBuilderExtensions.cs

在版本2.2中,管道末端的MVC中間件充當端點調度程序中間件。它將已解析的端點分派給適當的控制器操作。

端點解析中間件使用由MVC中間件配置的路由映射。

啟用端點路由后,如果添加以下自己的自定義中間件,則可以實際檢查已解析的端點對象:

using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseEndpointRouting(); app.UseHttpsRedirection(); //our custom middlware app.Use((context, next) => { var endpointFeature = context.Features[typeof(IEndpointFeature)] as IEndpointFeature; var endpoint = endpointFeature?.Endpoint; //note: endpoint will be null, if there was no resolved route if (endpoint != null) { var routePattern = (endpoint as RouteEndpoint)?.RoutePattern ?.RawText; Console.WriteLine("Name: " + endpoint.DisplayName); Console.WriteLine($"Route Pattern: {routePattern}"); Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata)); } return next(); }); app.UseMvc(); } 

如您所見,我們可以檢查並打印出端點路由解析中間件UseEndpointRouting已解決的端點對象 如果解析器無法將請求匹配到映射的路由,則端點對象將為null。我們需要引入另外兩個名稱空間來訪問端點路由功能。

版本3預覽3中的端點路由

在版本3預覽3中,端點路由將成為ASP.NET Core的完整公民,並且我們最終將在MVC控制器動作分派器和路由解析中間件之間實現分離。

這是版本3預覽版3中的端點啟動配置。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(routes => { routes.MapControllers(); }); app.UseAuthorization(); //No need to have a dispatcher middleware here. //The resolved endpoint is automatically dispatched to a controller action at the end //of the middleware pipeline //If an endpoint was not able to be resolved, a 404 not found is returned at the end //of the middleware pipeline } 

如您所見,我們有一種app.UseRouting()配置端點路由解析中間件的方法。該方法還采用匿名lambda函數,該函數配置路由解析器中間件將用來解析傳入請求端點的路由映射。

routes.MapControllers()映射函數內部配置默認的MVC路線。

您還將注意到,在此app.UseAuthorization()之后app.UseRouting()配置授權中間件。該中間件將有權訪問由端點路由中間件設置的httpcontext端點對象。

注意,在所有其他中間件配置之后,在方法末尾我們沒有配置任何MVC或端點調度中間件。

這是因為版本3預覽版3的行為是,解析的終結點將由框架本身隱式分派給控制器操作。

與我們針對2.2版所做的類似,我們可以添加相同的自定義中間件來檢查已解析的終結點對象。

using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(routes => { routes.MapControllers(); }); app.UseAuthorization(); //our custom middleware app.Use((context, next) => { var endpointFeature = context.Features[typeof(IEndpointFeature)] as IEndpointFeature; var endpoint = endpointFeature?.Endpoint; //note: endpoint will be null, if there was no //route match found for the request by the endpoint route resolver middleware if (endpoint != null) { var routePattern = (endpoint as RouteEndpoint)?.RoutePattern ?.RawText; Console.WriteLine("Name: " + endpoint.DisplayName); Console.WriteLine($"Route Pattern: {routePattern}"); Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata)); } return next(); }); //the endpoint is dispatched by default at the end of the middleware pipeline } 

即將發布的ASP.NET Core 3.0版源代碼存儲庫中的端點路由

當我們接近該框架的3.0版發布時,團隊似乎正在通過重新添加對端點分派器中間件配置的調用來使端點路由更加明確。他們還將路由映射配置選項移回了調度程序中間件配置方法。

通過查看當前的源代碼,我們可以再次看到這種變化。

以下是來自3.0版示例應用程序音樂商店的源代碼的片段:

https://github.com/aspnet/AspNetCore/blob/master/src/MusicStore/samples/MusicStore/Startup.cs

public void Configure(IApplicationBuilder app) { // Configure Session. app.UseSession(); // Add static files to the request pipeline app.UseStaticFiles(); // Add the endpoint routing matcher middleware to the request pipeline app.UseRouting(); // Add cookie-based authentication to the request pipeline app.UseAuthentication(); // Add the authorization middleware to the request pipeline app.UseAuthorization(); // Add endpoints to the request pipeline app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "areaRoute", pattern: "{area:exists}/{controller}/{action}", defaults: new { action = "Index" }); endpoints.MapControllerRoute( name: "default", pattern: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{id?}"); }); } 

如您所見,我們具有類似於我上面詳細介紹的偽代碼實現的東西。

特別是,我們仍然具有app.UseRouting()版本3預覽版3中的 中間件設置,但是現在,我們還有一個顯式的 app.UseEndpoints()終結點分發方法,該方法針對其作用更恰當地命名。

UseEndpoints是一種新的IApplicationBuilder擴展方法,提供了端點調度實現。

您可以在此處查看UseEndpointsUseRouting方法的源代碼

https://github.com/aspnet/AspNetCore/blob/master/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs

還要注意,路由映射配置lambda已從UseRouting中間件移至新的UseEndpoints中間件。

UseRouting終點的路線解析器將仍然可以訪問的映射來解決在請求處理時間的終點。即使UseEndpoints在啟動配置期間將它們傳遞到中間件。

將端點路由中間件添加到DI容器

要使用端點路由,我們還需要在該Startup.ConfigureServices方法中將中間件添加到DI容器中

2.2版中的ConfigureServices

對於該框架的2.2版,我們需要顯式添加對services.AddRouting()下面所示方法的調用,以將端點路由功能添加到DI容器:

public void ConfigureServices(IServiceCollection services) { services.AddRouting() services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } 

版本3預覽3中的ConfigureServices

對於版本3的預覽版3框架,端點路由已經在AddMvc()擴展方法的掩蓋下的DI容器中進行了配置

public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddNewtonsoftJson(); } 

使用端點路由和路由映射設置端點授權

使用版本3 Preview 3版本時,我們可以將授權元數據附加到端點。我們使用路由映射配置流暢的API RequireAuthorization方法進行此操作。

端點路由解析器在處理請求時將訪問此元數據,並將其添加到它在httpcontext上設置的Endpoint對象。

路由解析中間件之后的管道中的任何中間件都可以通過訪問已解析的Endpoint對象來訪問此授權數據。

特別是授權中間件可以使用此數據來做出授權決策。

當前,路由映射配置參數被傳遞到端點路由解析器中間件中,但是如前所述,在將來的版本中,路由映射配置將被傳遞到端點調度器中間件中。

無論哪種方式,附加的授權元數據都將可供端點解析器中間件使用。

以下是版本3預覽3 Startup.Configure方法的示例,其中我/secret向端點解析器中間件路由映射配置lambda參數添加了新路由:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(routes => { routes.MapControllers(); //Mapped route that gets attached authorization metadata using the RequireAuthorization extension method. //This metadata will be added to the resolved endpoint for this route by the endpoint resolver //The app.UseAuthorization() middleware later in the pipeline will get the resolved endpoint //for the /secret route and use the authorization metadata attached to the endpoint routes.MapGet("/secret", context => { return context.Response.WriteAsync("secret"); }).RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin" }); }); app.UseAuthentication(); //the Authorization middleware check the resolved endpoint object //to see if it requires authorization. If it does as in the case of //the "/secret" route, then it will authorize the route, if it the user is in the admin role app.UseAuthorization(); //the framework implicitly dispatches the endpoint at the end of the pipeline. } 

您可以看到我正在使用該RequireAuthorization方法向路由添加AuthorizeAttribute屬性/secret然后,將僅由端點分發發生之前的授權中間件授權以admin角色為用戶分發此路由。

正如我在3.3版預覽3中所展示的那樣,我們可以添加中間件來檢查httpcontext中解析的終結點對象,因此我們可以在這里檢查添加到終結點元數據中的AuthorizeAttribute:

using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authorization; public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(routes => { routes.MapControllers(); routes.MapGet("/secret", context => { return context.Response.WriteAsync("secret"); }).RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin" }); }); app.UseAuthentication(); //our custom middleware app.Use((context, next) => { var endpointFeature = context.Features[typeof(IEndpointFeature)] as IEndpointFeature; var endpoint = endpointFeature?.Endpoint; //note: endpoint will be null, if there was no //route match found for the request by the endpoint route resolver middleware if (endpoint != null) { var routePattern = (endpoint as RouteEndpoint)?.RoutePattern ?.RawText; Console.WriteLine("Name: " + endpoint.DisplayName); Console.WriteLine($"Route Pattern: {routePattern}"); Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata)); } return next(); }); app.UseAuthorization(); //the framework implicitly dispatches the endpoint here. } 

這次,我在授權中間件之前添加了自定義中間件,並引入了兩個其他名稱空間。

導航到/secret路線並檢查元數據,您可以看到它Microsoft.AspNetCore.Authorization.AuthorizeAttribute除了類型外還包含Microsoft.AspNetCore.Routing.HttpMethodMetadata類型。

本文使用的參考

以下文章包含了我用作本文參考的源材料:

https://devblogs.microsoft.com/aspnet/aspnet-core-3-preview-2/

https://www.stevejgordon.co.uk/asp-net-core-first-look-at-global-routing-dispatcher

vesrion 3預覽版4的更新

在我發布本文的那段時間,發布了ASP.NET Core 3.0預覽版4。Startup.cs創建新的webapi項目時,它添加了我在最新源代碼中描述的更改,如以下文件中的代碼段所示:

//code from Startup.cs file in a webapi project template using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddNewtonsoftJson(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); else app.UseHsts(); app.UseHttpsRedirection(); //add endpoint resolution middlware app.UseRouting(); app.UseAuthorization(); //add endpoint dispatch middleware app.UseEndpoints(endpoints => { //route map configuration endpoints.MapControllers(); //route map I added to show Authorization setup endpoints.MapGet("/secret", context => { return context.Response.WriteAsync("secret"); }).RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin" }); }); } 

如您所見,此版本UseEndpoints在添加端點調度中間件的管道的末尾添加了顯式中間件擴展方法。路由配置參數也已從UseRouting預覽3中方法移動UseEndpoints預覽4中。

結論

端點路由允許ASP.NET Core應用程序在中間件管道的早期確定要調度的端點,以便以后的中間件可以使用該信息來提供當前管道配置無法提供的功能。

這使ASP.NET Core框架更加靈活,因為它使路由匹配和解析功能與終結點調度功能脫鈎,而終結點調度功能迄今都與MVC中間件捆綁在一起。

本文作者:作者Areg Sarkissian

翻譯至https://aregcode.com/blog/2019/dotnetcore-understanding-aspnet-endpoint-routing/


免責聲明!

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



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