这篇文章是 ASP.NET 6 中间件系列文章的第 3 部分,你还可以阅读第1部分和第2部分。
我们通过中间件创建的管道是有执行顺序的,执行顺序与中间件的添加顺序是相同的,接下来我们讨论一下为什么要有执行顺序,以及它的重要性。
示例项目
在 GitHub 上可以获得这篇文章涉及到的代码:
https://github.com/zilor-net/ASPNET6Middleware/tree/Part3
执行顺序
在本系列的第1部分中,中间件构成了一个管道,该管道中的中间件按照一定的顺序执行,如下图所示:
请求按顺序经过各个中间件,而响应则按相反的顺序返回。
在前面的文章中,我们已经定义了两个中间件类:
LoggingMiddleware
用于记录请求/响应日志;SimpleResponseMiddleware
用于中断管道,返回响应。
在这篇文章中,我们仍然以LoggingMiddleware
为例:
app.UseLoggingMiddleware();
添加延迟
我们创建一个新的中间件类,叫做IntentionalDelayMiddleware
,它看起来像这样:
namespace MiddlewareNET6Demo.Middleware
{public class IntentionalDelayMiddleware{private readonly RequestDelegate _next;public IntentionalDelayMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){await Task.Delay(100);await _next(context);await Task.Delay(100);}}
}
这个中间件在处理传入请求和处理传出响应时都会等待 100ms,总等待时间为 200ms。
当然,实际场景下,我们并不会这么做。
在这里,IntentionalDelayMiddleware
只是代表了某种未定义的中间件,它需要一个可预测的时间来执行。
我们需要向管道中添加一个IntentionalDelayMiddleware
的实例。问题在于,我们是在LoggingMiddleware
之前还是之后添加它?
其实在这种情况下,这个问题可能并不重要,因为这两个中间件不会进行交互,也不处理相同的事情。
在这个示例中,让我们在LoggingMiddleware
之后添加IntentionalDelayMiddleware
:
app.UseLoggingMiddleware();
app.UseIntentionalDelayMiddleware();
如果现在运行应用程序,我们可能不会发现明显的差异,因为 200 毫秒相当快。
添加执行时间中间件
为了监视每个请求的所消耗的时间,我们往往需要记录每个请求到我们系统的执行时间。
这个需求对于中间件来说是非常简单的,我们可以使用 .NET 提供的Stopwatch
类和第2篇文章中创建的LoggingService
来实现。
下面是名为TimeLoggingMiddleware
的中间件类:
using MiddlewareNET6Demo.Logging;
using System.Diagnostics;namespace MiddlewareNET6Demo.Middleware
{public class TimeLoggingMiddleware{private readonly RequestDelegate _next;private readonly ILoggingService _logger;public TimeLoggingMiddleware(RequestDelegate next, ILoggingService logger){_next = next;_logger = logger;}public async Task InvokeAsync(HttpContext context){Stopwatch watch = new Stopwatch();watch.Start();await _next(context);watch.Stop();_logger.Log(LogLevel.Information, "Time to execute: " + watch.ElapsedMilliseconds + " milliseconds.");}}
}
我们需要将其添加到管道中。但是,这里仍然有个问题:我们应该添加到哪个位置?
如果我们将TimeLoggingMiddleware
添加到IntentionalDelayMiddleware
之前,那么后者所引起的延迟将包含在前者所度量的范围中。
如果我们将TimeLoggingMiddleware
添加到IntentionalDelayMiddleware
之后,那么后者所引起的延迟将不会包含在前者所度量的范围中。
让我们来看看管道:
app.UseHttpsRedirection();
app.UseStaticFiles();// 如果该中间件发生任何延迟,那么该延迟不会包含在时间日志中。
app.UseLoggingMiddleware();// 时间记录中间件
app.UseTimeLoggingMiddleware();// 延迟中间件。
// 此时,延迟被包含在时间日志中。
app.UseIntentionalDelayMiddleware();app.UseRouting();app.UseAuthorization();app.MapRazorPages();app.Run();
在这个 Program.cs 文件中,哪个位置更适合放置TimeLoggingMiddleware
?其答案取决于几个问题:
时间日志需要包括诸如无效授权之类的执行时间吗?如果是,那么必须在调用
app.UseAuthorization()
之前,放置TimeLoggingMiddleware
。路由调用需要的时间非常少,但可以测量。我们要把它包括进去吗?如果是,就必须在调用
app.UseRouting()
之前,放置TimeLoggingMiddleware
。
像大多数现实世界的问题一样,这个问题没有明确的答案。
如果没有明确的指示,那么这最终需要由开发人员根据系统的具体情况来做出决定。
需要注意的是:
app.UseIntentionalDelayMiddleware();
app.UseTimeLoggingMiddleware();
这两个是完全不同的:
app.UseTimeLoggingMiddleware();
app.UseIntentionalDelayMiddleware();
这就是为什么中间件在管道中顺序很重要的一个例子。