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. 注意事项:避开中间件的陷阱

  1. 生命周期管理:避免在中间件中直接使用Scoped服务
  2. 异步陷阱:确保正确处理异步操作
  3. 过早响应:调用Response.Write后不要继续执行_next
  4. 性能监控:中间件的执行时间计入整体响应时间
  5. 异常传播:正确使用try-catch或异常处理中间件
  6. 顺序敏感性:中间件顺序影响功能有效性

8. 实战总结:中间件开发最佳实践

通过本文案例可以看到,成功的中间件开发需要:

  1. 明确职责边界
  2. 遵守单一职责原则
  3. 保持轻量级设计
  4. 提供扩展配置选项
  5. 完善的日志记录
  6. 全面的异常处理

当我们需要处理全局性、基础性的请求处理逻辑时,中间件是不二之选。但也要警惕过度设计,记住中间件不是解决所有问题的银弹。