写入自定义 ASP.NET Core 中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 ASP.NET Core 提供了一组丰富的内置中间件组件,但在某些情况下,你可能需要写入自定义中间件。
自定义中间件类
通常,中间件封装在类中,并且通过扩展方法公开。
一、内联中间件(不推荐)
请考虑以下内联中间件,该中间件通过查询字符串设置当前请求的区域性:
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
//内联中间件
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline.
await next(context);
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});
app.Run();
Use
扩展可以使用两个重载:
- 一个重载采用 HttpContext 和
Func<Task>
。 不使用任何参数调用Func<Task>
。 - 另一个重载采用
HttpContext
和 RequestDelegate。 通过传递HttpContext
调用RequestDelegate
。
二、中间件封装在类中(推荐)
using System.Globalization; namespace Middleware.Example; public class RequestCultureMiddleware { private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { var culture = new CultureInfo(cultureQuery); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline. await _next(context); } } public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } }
中间件类(RequestCultureMiddleware)必须包括:
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为
Invoke
或InvokeAsync
的公共方法。 此方法必须:- 返回
Task
。 - 接受类型 HttpContext 的第一个参数。
- 返回
构造函数和 Invoke
/InvokeAsync
的其他参数由依赖关系注入 (DI) 填充。
通过 Program.cs
调用中间件:
using Middleware.Example; using System.Globalization; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseHttpsRedirection(); app.UseRequestCulture(); app.Run(async (context) => { await context.Response.WriteAsync( $"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}"); }); app.Run();
中间件依赖项(即:给中间件传入服务类)
中间件应通过在其构造函数中公开其依赖项来遵循显式依赖项原则。 在每个应用程序生存期构造一次中间件。
中间件组件可通过构造函数参数从依赖关系注入 (DI) 解析其依赖项。 此外,UseMiddleware 还可直接接受其他参数。
按请求中间件依赖项
中间件在应用启动时构造,因此具有应用程序生存期。 在每个请求过程中,中间件构造函数使用的范围内生存期服务不与其他依赖关系注入类型共享。 若要在中间件和其他类型之间共享范围内服务,请将这些服务添加到 InvokeAsync
方法的签名。 InvokeAsync
方法可接受由 DI 填充的其他参数:
namespace Middleware.Example; public class MyCustomMiddleware { private readonly RequestDelegate _next; public MyCustomMiddleware(RequestDelegate next) { _next = next; } // IMessageWriter is injected into InvokeAsync public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc) { svc.Write(DateTime.Now.Ticks.ToString()); await _next(httpContext); } } public static class MyCustomMiddlewareExtensions { public static IApplicationBuilder UseMyCustomMiddleware( this IApplicationBuilder builder) { return builder.UseMiddleware<MyCustomMiddleware>(); } }
生存期和注册选项包含范围内生存期服务的中间件的完整示例。
以下代码用于测试以上中间件:
using Middleware.Example; var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseMyCustomMiddleware(); app.MapGet("/", () => "Hello World!"); app.Run();
IMessageWriter
接口和实现:
namespace Middleware.Example; public interface IMessageWriter { void Write(string message); } public class LoggingMessageWriter : IMessageWriter { private readonly ILogger<LoggingMessageWriter> _logger; public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) => _logger = logger; public void Write(string message) => _logger.LogInformation(message); }