中间件是一种装配到应用管道以处理请求和响应的软件。 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();
以上突出显示的内联中间件用于演示通过调用 Microsoft.AspNetCore.Builder.UseExtensions.Use 创建中间件组件。 以上 Use
扩展方法将内联定义的中间件委托添加到应用程序的请求管道。
Use
扩展可以使用两个重载:
- 一个重载采用 HttpContext 和
Func<Task>
。 不使用任何参数调用Func<Task>
。 - 另一个重载采用
HttpContext
和 RequestDelegate。 通过传递HttpContext
调用RequestDelegate
。
优先使用后面的重载,因为它省去了使用其他重载时所需的两个内部每请求分配。
通过传入区域性测试中间件。 例如,请求 https://localhost:5001/?culture=es-es
。
有关 ASP.NET Core 的内置本地化支持,请参阅ASP.NET Core 中的全球化和本地化。
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); } }
必须包括中间件类:
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为
Invoke
或InvokeAsync
的公共方法。 此方法必须:- 返回
Task
。 - 接受类型 HttpContext 的第一个参数。
- 返回
构造函数和 Invoke
/InvokeAsync
的其他参数由依赖关系注入 (DI) 填充。
通常,创建扩展方法以通过 IApplicationBuilder 公开中间件:
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>(); } }
中间件依赖项
中间件应通过在其构造函数中公开其依赖项来遵循显式依赖项原则。 在每个应用程序生存期构造一次中间件。
中间件组件可通过构造函数参数从依赖关系注入 (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);
}