一、什么是ASP.NET Core中间件

中间件是ASP.NET Core管道中的核心组件,可以把它想象成洋葱的一层层皮。每个请求都会依次穿过这些中间件层,每个中间件都可以对请求进行处理或者修改。比如我们常见的静态文件处理、身份验证、路由等功能,都是通过中间件实现的。

想象一下你去银行办业务的场景:进门先取号(路由中间件),然后保安检查证件(认证中间件),柜台办理业务(业务逻辑中间件),最后拿到回执(响应处理中间件)。这就是中间件的工作方式。

二、自定义中间件开发实战

让我们通过一个实际的例子来创建一个简单的日志记录中间件。这个中间件会记录每个请求的进入和离开时间。

// 技术栈:ASP.NET Core 6.0
public class RequestLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggerMiddleware> _logger;

    // 构造函数注入ILogger服务
    public RequestLoggerMiddleware(
        RequestDelegate next, 
        ILogger<RequestLoggerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 请求进入时记录
        var startTime = DateTime.UtcNow;
        _logger.LogInformation($"Request started: {context.Request.Path} at {startTime}");

        // 调用管道中的下一个中间件
        await _next(context);

        // 请求完成时记录
        var endTime = DateTime.UtcNow;
        var duration = endTime - startTime;
        _logger.LogInformation($"Request completed: {context.Request.Path} in {duration.TotalMilliseconds}ms");
    }
}

// 扩展方法,方便在Startup中使用
public static class RequestLoggerMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggerMiddleware>();
    }
}

在Startup.cs中使用这个中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他中间件...
    app.UseRequestLogger(); // 添加我们的日志中间件
    // 其他中间件...
}

这个中间件现在可以记录每个请求的耗时,对于性能监控非常有用。

三、中间件的执行顺序

中间件的执行顺序非常重要,就像安检必须在登机前完成一样。ASP.NET Core中间件是按照添加到管道的顺序执行的,但响应时是反向的。

让我们看一个更复杂的例子:

public void Configure(IApplicationBuilder app)
{
    // 异常处理应该放在最前面
    app.UseExceptionHandler("/Error");
    
    // HTTPS重定向
    app.UseHttpsRedirection();
    
    // 静态文件中间件
    app.UseStaticFiles();
    
    // 路由中间件
    app.UseRouting();
    
    // 认证中间件
    app.UseAuthentication();
    app.UseAuthorization();
    
    // 我们的自定义日志中间件
    app.UseRequestLogger();
    
    // 终结点中间件
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

执行顺序说明:

  1. 请求进入时:从上往下执行
  2. 响应返回时:从下往上执行

如果把日志中间件放在最前面,它将无法记录后续中间件处理的时间。如果把认证中间件放在路由后面,路由可能无法获取用户信息。

四、中间件中的依赖注入

中间件支持完整的依赖注入,这是ASP.NET Core的一大优势。我们来看一个更复杂的例子,它使用到了多个注入的服务。

public class PerformanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<PerformanceMiddleware> _logger;
    private readonly IMemoryCache _cache;
    private readonly IConfiguration _config;

    public PerformanceMiddleware(
        RequestDelegate next,
        ILogger<PerformanceMiddleware> logger,
        IMemoryCache cache,
        IConfiguration config)
    {
        _next = next;
        _logger = logger;
        _cache = cache;
        _config = config;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var watch = Stopwatch.StartNew();
        
        // 检查配置是否启用性能监控
        var enabled = _config.GetValue<bool>("Performance:Enabled");
        if (!enabled)
        {
            await _next(context);
            return;
        }

        // 调用下一个中间件
        await _next(context);
        
        watch.Stop();
        
        // 如果响应时间超过阈值,记录警告
        var threshold = _config.GetValue<int>("Performance:ThresholdMs");
        if (watch.ElapsedMilliseconds > threshold)
        {
            _logger.LogWarning($"慢请求: {context.Request.Path} 耗时 {watch.ElapsedMilliseconds}ms");
            
            // 将慢请求信息缓存起来供后续分析
            var slowRequests = _cache.GetOrCreate("SlowRequests", entry => 
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
                return new ConcurrentBag<SlowRequestInfo>();
            });
            
            slowRequests?.Add(new SlowRequestInfo
            {
                Path = context.Request.Path,
                Duration = watch.ElapsedMilliseconds,
                Timestamp = DateTime.UtcNow
            });
        }
    }
}

public class SlowRequestInfo
{
    public string Path { get; set; }
    public long Duration { get; set; }
    public DateTime Timestamp { get; set; }
}

这个中间件展示了如何注入和使用多个服务:

  • ILogger用于记录日志
  • IMemoryCache用于缓存慢请求信息
  • IConfiguration用于读取配置

五、高级中间件模式

有时候我们需要根据条件动态决定是否使用某个中间件。ASP.NET Core提供了Map和MapWhen等方法来实现这种模式。

public void Configure(IApplicationBuilder app)
{
    // 基本中间件...
    
    // 只为/api开头的路径添加性能监控
    app.MapWhen(
        context => context.Request.Path.StartsWithSegments("/api"),
        apiBranch =>
        {
            apiBranch.UseMiddleware<PerformanceMiddleware>();
            apiBranch.UseRouting();
            apiBranch.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        });
    
    // 其他路径走常规处理
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

MapWhen让我们可以基于请求的某些特征(如路径、请求头等)来有条件地应用中间件。这在微服务架构中特别有用,比如只为特定服务启用某些功能。

六、实际应用场景与最佳实践

  1. 应用场景

    • 请求/响应日志记录
    • 性能监控
    • 异常处理
    • 认证授权
    • 缓存控制
    • API版本控制
    • 请求限流
  2. 技术优缺点

    • 优点:
      • 高度模块化,每个中间件只关注单一职责
      • 灵活的管道配置
      • 支持依赖注入
      • 性能高效
    • 缺点:
      • 执行顺序容易出错
      • 过度使用会导致管道复杂
  3. 注意事项

    • 异常处理中间件应该放在管道的最前面
    • 静态文件中间件应该在路由中间件之前
    • 认证中间件应该在路由之后、终结点之前
    • 避免在中间件中做耗时操作,会影响整体性能
  4. 总结: ASP.NET Core中间件是一个非常强大的功能,理解它的工作原理和最佳实践对于构建高效、可维护的Web应用至关重要。通过自定义中间件,我们可以轻松实现各种横切关注点,保持业务代码的简洁性。记住中间件的执行顺序是关键,合理的依赖注入使用能让中间件更加强大和灵活。