1. 中间件:Web应用的流水线工人
想象你走进一家汽车工厂,每辆汽车在装配线上都要经历焊接、喷漆、组装等工序。ASP.NET Core的中间件就是这些工序的操作工人,每个工人(中间件)负责处理特定的任务。当HTTP请求进入应用程序时,这个"汽车"会依次经过所有工人的加工处理。
基础中间件示例:
// 简易日志记录中间件
public class SimpleLoggingMiddleware
{
private readonly RequestDelegate _next;
// next参数代表管道中的下一个中间件
public SimpleLoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// 请求到达时记录
Console.WriteLine($"Request started: {context.Request.Path}");
// 传递给下一个中间件
await _next(context);
// 响应返回时记录
Console.WriteLine($"Response sent: {context.Response.StatusCode}");
}
}
(技术栈:ASP.NET Core 6.0)
2. 打造你的专属中间件
2.1 基础版中间件开发
要实现自定义中间件,我们可以选择继承IMiddleware接口或者使用约定方式。推荐使用后者,因为它更灵活:
// 请求耗时统计中间件
public class TimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<TimingMiddleware> _logger;
// 通过构造函数注入日志服务
public TimingMiddleware(RequestDelegate next, ILogger<TimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
await _next(context);
stopwatch.Stop();
_logger.LogInformation($"请求 {context.Request.Path} 耗时 {stopwatch.ElapsedMilliseconds}ms");
}
}
(技术栈:ASP.NET Core 6.0)
2.2 带参数的智能中间件
当需要动态配置中间件时,可以使用选项模式:
// IP白名单中间件(带配置)
public class IpWhitelistMiddleware
{
private readonly RequestDelegate _next;
private readonly List<IPAddress> _allowedIps;
public IpWhitelistMiddleware(RequestDelegate next, IOptions<IpFilterOptions> options)
{
_next = next;
_allowedIps = options.Value.AllowedIPs
.Select(IPAddress.Parse)
.ToList();
}
public async Task InvokeAsync(HttpContext context)
{
var remoteIp = context.Connection.RemoteIpAddress;
if (!_allowedIps.Contains(remoteIp))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
await _next(context);
}
}
// 配置类
public class IpFilterOptions
{
public List<string> AllowedIPs { get; set; } = new();
}
(技术栈:ASP.NET Core 6.0)
3. 中间件编排:流水线的艺术
3.1 执行顺序的蝴蝶效应
中间件的注册顺序直接影响请求处理流程,试比较以下两种配置:
正确顺序:
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<TimingMiddleware>();
app.UseEndpoints(endpoints => { /* ... */ });
错误顺序可能导致的问题:
// 如果先注册授权中间件,后注册认证中间件
app.UseAuthorization(); // 无效配置!
app.UseAuthentication();
3.2 分支映射实战
使用Map扩展创建中间件分支:
// API版本控制分支
app.Map("/api/v1", v1App =>
{
v1App.UseMiddleware<VersionHeaderMiddleware>("v1");
v1App.UseEndpoints(endpoints => endpoints.MapControllers());
});
app.Map("/api/v2", v2App =>
{
v2App.UseMiddleware<VersionHeaderMiddleware>("v2");
v2App.UseEndpoints(endpoints => endpoints.MapControllers());
});
4. 依赖注入:中间件的武器库
4.1 服务获取的三重奏
方式一:构造函数注入(推荐)
public class DbLogMiddleware
{
private readonly RequestDelegate _next;
private readonly AppDbContext _db;
public DbLogMiddleware(RequestDelegate next, AppDbContext db)
{
_next = next;
_db = db;
}
public async Task InvokeAsync(HttpContext context)
{
// 使用_db记录日志到数据库
await _next(context);
}
}
方式二:Invoke方法参数注入
public class ServiceLocatorMiddleware
{
private readonly RequestDelegate _next;
public ServiceLocatorMiddleware(RequestDelegate next)
{
_next = next;
}
// 通过方法参数获取服务
public async Task InvokeAsync(HttpContext context, IEmailService emailService)
{
// 使用emailService发送通知
await _next(context);
}
}
5. 应用场景:何时使用中间件这把瑞士军刀
典型用例:
- 全局请求日志记录(如耗时统计)
- 自定义身份验证/授权流程
- API版本控制
- 流量控制与限速
- 响应标准化处理
- 跨域请求配置
- 请求/响应修改(如添加自定义头)
vs 过滤器:应用场景对比
中间件更适合处理与具体业务无关的基础设施功能,而过滤器适合处理与业务逻辑密切相关的横切关注点。
6. 优缺点分析:看清中间件的两面性
优势:
- 全局作用范围
- 完全控制请求生命周期
- 高度可复用
- 支持管道短路
- 良好的DI支持
局限:
- 对MVC内部机制无感知
- 不适合业务逻辑处理
- 调试复杂度较高
- 过度使用可能影响性能
7. 注意事项:避开中间件的陷阱
- 生命周期管理:避免在中间件中直接使用Scoped服务
- 异步陷阱:确保正确处理异步操作
- 过早响应:调用Response.Write后不要继续执行_next
- 性能监控:中间件的执行时间计入整体响应时间
- 异常传播:正确使用try-catch或异常处理中间件
- 顺序敏感性:中间件顺序影响功能有效性
8. 实战总结:中间件开发最佳实践
通过本文案例可以看到,成功的中间件开发需要:
- 明确职责边界
- 遵守单一职责原则
- 保持轻量级设计
- 提供扩展配置选项
- 完善的日志记录
- 全面的异常处理
当我们需要处理全局性、基础性的请求处理逻辑时,中间件是不二之选。但也要警惕过度设计,记住中间件不是解决所有问题的银弹。
Comments