手把手带你定制.NET 6.0的Middleware中间件
前言
在本文中,我们将学习中间件,以及如何使用它入一步定制应用程序。我们将快速学习中间件的基础知识,然后探讨如何使用它做的一些特别事情。
本文涵盖的主题包括:
- 中间件简介
- 编写自定义中间件
- 中间件的潜力
- 如何使用中间件
本章所处的位置,如下图所示:
技术预备
我们使用控制台、shell或Bash终端先创建一个ASP.NET Core MVC应用程序,然后切换到工作目录:
dotnet new web -n MiddlewaresDemo -o MiddlewaresDemo
然后用VS打开项目:
cd MiddlewaresDemo code .
注重在.NET 6.0中,web项目模板发生了变化。Microsoft引进了minimal API,项目模板默认使用minimal API。
中间件简介
大多数人可能已经知道中间件是什么,但有些人可能不知道,即使你已经在使用ASP.NET Core有一段时间了。我们一般不需要具体了解中间件实例,因为它们大多隐躲在扩铺方法后面,例如UseMvc()、UseAuthentication()、UseDeveloperExceptionPage()等。每次在Configure方法中,我们默认将隐式地使用至少一个或更多个中间件组件。
中间件组件是处理哀求管道的一段代码。我们可以将哀求流程想象成一串管道,每次哀求调用,都会返归一个响应。中间件负责创建归声——它操纵哀求上下文,加工处理、叠加逻辑、丰富信息。
中间件组件按配置顺序执行。配置的第一个中间件组件是第一个执行的组件。我们可以把中间件观成归旋镖,出往的时候第一个执行,归来的时候最后一个执行。
在ASP.NET Core web应用程序,假如客户端哀求的是图像或任何其他静态文件,StaticFileMiddleware将负责查找该资源,假如找到该资源,则返归该资源。假如没有,这个中间件除了调用下一个之外什么都不做。
MvcMiddleware组件检查哀求的资源,将其映射到已配置的路由,执行控制器,创建视图,并返归HTML或Web API结果。假如MvcMiddleware没有找到匹配的控制器,它无论如何都会返归一个结果——通常是一个404状态的结果,这就是为什么MvcMiddleware是最后配置的中间件。
异常处理中间件通常是配置的第一批的中间件之一,不是因为它是第一个执行的,而是因为它是最后一个执行的。异常处理验证结果,并以客户端友好的方式在浏览器中显示可能的异常。以下过程描述了运行时发生的500错误状态:
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
在ASP.NET Core 6.0,Microsoft引进了minimal API,它简化了应用配置,并隐躲了许多默认配置,比如隐式的using
声明,因此,在头部我们观不到任何using
语句,以上就是我们观到的ASP.NET Core 6.0中的Program.cs
文件内容。
在这里,lambda中间件绑定到默认路由,只有一句简朴的“Hello World!”响应流。这个特别的中间件会终止管道并返归响应内容。因此,它是最后一个运行的中间件。
下面我们把app.MapGet()做个替换,如下所示:
app.Use(async (context, next) =>{ await context.Response.WriteAsync("==="); await next(); await context.Response.WriteAsync("==="); }); app.Use(async (context, next) => { await context.Response.WriteAsync(">>>>>> "); await next(); await context.Response.WriteAsync(" <<<<<<"); }); app.Run(async context => { await context.Response.WriteAsync("Hello World!"); });
这里调用两个app.Use()
方法,并且创建了两个lambda中间件,除了做简朴的处理外,中间件还调用了它们的后继组件,每个中间件的调用链很明确很清楚。在调用下一个中间件之前,处理实际的哀求,在调用下个中间件之后,处理响应。以上就是管道的工作机制。
假如现在运行程序(使用dotnet run)并在浏览器中打开URL,我们应该会观到这样的纯文本结果
===>>>>>> Hello World! <<<<<<===
不知道您理解了没?假如理解了,我们去下学习,观观如何使用这个概念向哀求管道添加一些附加功能。
编写自定义中间件
中间件可以说是ASP.NET Core的基座,在哀求期间执行的所有逻辑都基于此机制。因此,我们可以使用它向web添加自定义功能。在下面案例,我们希望找出通过哀求管道的每个哀求的执行时间:
我们可以在调用下一个中间件之前创建并启动秒表,然后在调用下个中间件之后停止测量执行时间,如下所示:
app.Use(async (context, next) => { var s = new Stopwatch(); s.Start(); //其他操作 await next(); s.Stop(); //结束度量 var result = s.ElapsedMilliseconds; //统计耗时 await context.Response.WriteAsync($"耗时:{result} 秒。"); });
记得为System.Diagnostics
添加using
语句。
之后,我们将经过的毫秒返归到响应流。
假如您编写的中间件组件很多,Program.cs
将变得非常混乱。所以大多数中间件组件将被编写为独立的类,如下所示:
using System.Diagnostics; public class StopwatchMiddleware { private readonly RequestDelegate _next; public StopwatchMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var s = new Stopwatch(); s.Start(); //其他操作 await _next(context); s.Stop(); //结束度量 var result = s.ElapsedMilliseconds; //统计耗时 await context.Response.WriteAsync($"耗时:{result} 秒。"); } }
在Invoke方法中的,我们获得构造函数和当前上下文获得要执行的下一个中间件组件。
注重:
中间件在应用程序启动时初始化,构造函数在应用程序生命周期内仅运行一次。另一方面,每个哀求调用一次Invoke方法。
要使用此中间件,您可以使用一个通用的UseMiddleware方法:
app.UseMiddleware<StopwatchMiddleware>();
然而,更优雅的方法是创建一个封装此调用的扩铺方法:
public static class StopwatchMiddlewareExtension { public static IApplicationBuilder UseStopwatch(this IApplicationBuilder app) { app.UseMiddleware<StopwatchMiddleware>(); return app; } }
然后就可以这样使用:
app.UseStopwatch();
这样,您可以通过哀求管道向ASP.NET Core应用程序提供其他功能。中间件中提供了整个HttpContext
。这样,您可以使用中间件操纵哀求和响应。
例如,AuthenticationMiddleware
尝试从哀求中收集用户信息。假如找不到任何信息,它将通过向客户端发送特定的响应来哀求信息。假如它找到,它会将其添加到哀求上下文中,并以这种方式将其提供应整个应用程序。
中间件的潜力
使用中间件还可以做许多其他事情。例如,可以将哀求管道拆分为两个或多个管道,我们将在这里讨论如何做到这一点。
使用/map分支管道
下一段代码显示了如何基于特定路径创建哀求管道的分支:
app.Map("/map1", app1 => { // 其他中间件 app1.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); }); app.Map("/map2", app2 => { // 其他中间件 app2.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); }); // 其他中间件
/map1路径是一个特定的分支,它在内部继承哀求管道,/map2与此相同。这两个map都有自己内部的中间件配置。所有其他未指定的路径都遵循该主分支。
使用MapWhen分支管道
还有一个MapWhen方法可以根据条件分支管道,而不是根据路径分支:
public void Configure(IApplicationBuilder app) { app.MapWhen(context =>context.Request.Query.ContainsKey("分支"), app1 => { // 其他中间件 app1.Run(async context => { await context.Response.WriteAsync( "MapBranch Test"); }); }); //其他中间件 app.Run(async context => { await context.Response.WriteAsync("Hello non-Map."); }); }
使用中间件构造条件
我们一般可以根据配置值创建条件,或者根据哀求上下文的属性创建条件。在前面的示例中,我们使用了查询字符串属性作为条件。当然,你也可以使用HTTP标头、表单属性或哀求上下文的任何其他属性。
假如需要,还可以嵌套map以创建子分支和孙分支。
我们再观下健康检查中间件,ASP.NET Core HealthCheck API
的工作原理如下:
首先,它使用MapWhen指定要使用的端口,然后,它使用Map
设置HealthCheck API
路径(假如未指定端口则使用Map)。最后,使用了HealthCheckMiddleware
。我们观下面的代码示例:
private static void UseHealthChecksCore(IApplicationBuilder app, PathString path, int? port, object[] args) { if (port == null) { app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>(args)); } else { app.MapWhen(c => c.Connection.LocalPort == port, b0 => b0.Map(path, b1 =>b1.UseMiddleware<HealthCheckMiddleware>(args))); }; }
这里,我们可以使用Map或MapWhen分别基于特定路径或特定条件提供特别的API或资源。
接下来,让我们观观如何在更新版本的ASP.NET Core
中使用终止中间件组件。
在ASP.NET Core 3.0及更高版本中使用中间件
在ASP.NET Core 3.0
及更高版本,有两种新的中间件,它们被称为UseRouting
和UseEndpoints
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); }
第一个是使用路由的中间件UseRouting
,另一个是访问地址的UseEndpoints
。
这是新的端点路由。以前,路由是MVC的一部分,它只适用于MVC、Web API和基于MVC的框架。然而在ASP.NET Core 3.0及更高版本,路由不再是MVC框架中的一部分。现在,MVC和其他框架都可以被映射到特定的路由或端点。
在前面的代码段中,GET哀求被映射到页面根URL。在下一个代码片段中,MVC被映射到路由模式,RazorPages被映射到基于RazorPage的特定文件结构的路由:
app.UseEndpoints(endpoints => { endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); });
现在已经没有UseMvc方法了,即使它仍旧存在并在IApplicationBuilder
对象级别上工作,以防止现有代码中断。现在,激活ASP.NET Core功能的方法更为精细。
- Areas for MVC and web API:
endpoints.MapAreaControllerRoute(...);
- MVC and web API:
endpoints.MapControllerRoute(...);
- Blazor server-side:
endpoints.MapBlazorHub(...);
- SignalR:
endpoints.MapHub(...);
- Razor Pages:
endpoints.MapRazorPages(...);
- Health checks:
endpoints.MapHealthChecks(...);
这些是ASP最常用的新Map方法。
还有很多方法可以定义归退地址,比如将路由和HTTP方法映射到代理,以及中间件组件。
你可以创建适用于所有哀求的中间件,例如StopWatchMiddleware
,你也可以编写中间件以在特定路径或路由上工作,例如创建一个Map方法,以将其映射到该路由。
注重事项
不再建议在中间件内部处理路由。相反,您应该使用新的地址路由。使用这种方法,中间件更加通用,它可以通过单一的配置就可以在多个路由上工作。
重写终止中间件
接下来,我们创建小型虚拟中间件,将应用程序状态写进特定路由。在此示例中,没有自定义路由处理:
namespace MiddlewaresSample; public class AppStatusMiddleware { private readonly RequestDelegate _next; private readonly string _status; public AppStatusMiddleware(RequestDelegate next, string status) { _next = next; _status = status; } public async Task Invoke(HttpContext context) { await context.Response.WriteAsync($"Hello {_status}!"); } }
我们需要做的是在IEndpointRouteBuilder
对象上编写一个扩铺方法。此方法将路由模式作为可选参数,并返归IEndpointConventionBuilder
对象以启用跨域资源共享(CORS)、身份验证或路由的其他条件。
现在,我们应该添加一个扩铺方法,以便更轻易地使用中间件:
public static class MapAppStatusMiddlewareExtension { public static IEndpointConventionBuilder MapAppStatus(this IEndpointRouteBuilder routes, string pattern = "/", string name = "World") { var pipeline = routes.CreateApplicationBuilder().UseMiddleware<AppStatusMiddleware>(name).Build(); return routes.Map(pattern, pipeline).WithDisplayName("AppStatusMiddleware"); } }
完成后,我们可以使用MapAppStatus方法将其映射到特定路线:
app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", () => "Hello World!"); endpoints.MapAppStatus("/status", "Status"); });
现在,我们可以通过输进以下地址在浏览器中调用路由: http://localhost:5000/status
总结
大多数ASP.NET Core功能基于中间件,在本章中,我们学习了中间件的工作原理以及如何创建自己的中间件组件来扩铺ASP.NET框架。我们还学习了如何使用新路由向自定义的终止中间件添加路由。
在下一章中,我们将了解ASP.NET Core中的新端点路由,它答应我们以简朴灵活的方式创建自己的托管端点。
到此这篇关于定制.NET 6.0的Middleware中间件的文章就介绍到这了,更多相关定制.NET 6.0 Middleware中间件内容请搜索以前的文章或继承浏览下面的相关文章希望大家以后多多支持!