自定义中间件进入管道中执行队列的流程

自定义中间件进入管道中执行队列的流程

1. 自定义中间件类的标准写法

public class CustomLoggingMiddleware
{
    private readonly RequestDelegate _next;
    
    // 1. 构造函数必须包含 RequestDelegate next 参数
    public CustomLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // 2. 必须包含名为 Invoke 或 InvokeAsync 的公共方法
    public async Task InvokeAsync(HttpContext context)
    {
        //执行前动作
        ...
        
        // 调用管道中的下一个中间件
        await _next(context);       

        //执行后动作
        ...
    }
}

2. 使用扩展方法注册中间件

public static class CustomLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomLogging(this IApplicationBuilder app)
    {
        return app.UseMiddleware<CustomLoggingMiddleware>();
    }
}

3. 在管道中使用

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseCustomLogging();  // 我们的自定义中间件
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.MapControllers();
}

4. 转换流程详解

现在来看 UseMiddleware<CustomLoggingMiddleware>() 是如何将类转换成管道方法的:

步骤 1: UseMiddleware<T> 扩展方法
// Microsoft.AspNetCore.Builder.UseMiddlewareExtensions
public static class UseMiddlewareExtensions
{
    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app)
    {
        return app.UseMiddleware(typeof(TMiddleware));
    }

    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middlewareType)
    {
        // 创建中间件工厂
        return app.Use(next =>
        {
            // 这里就是转换的关键!
            // 创建一个委托,它会在每次请求时实例化中间件并调用Invoke方法
            var middlewareFactory = (IMiddlewareFactory)app.ApplicationServices
                .GetService(typeof(IMiddlewareFactory));

            return async context =>
            {
                // 每次请求都会执行这个委托
                var middleware = middlewareFactory.Create(middlewareType);
                try
                {
                    // 调用中间件的InvokeAsync方法
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }
}
步骤 2: 运行时实际生成的委托

实际上,ASP.NET Core 使用更高效的代码生成机制。简化后的等效代码:

// 运行时生成的委托大致如下:
Func<RequestDelegate, RequestDelegate> generatedMiddleware = next =>
{
    return async context =>
    {
        // 创建中间件实例
        var middleware = new CustomLoggingMiddleware(next, logger);
        
        // 调用InvokeAsync方法
        await middleware.InvokeAsync(context);
    };
};
步骤 3: 管道构建过程
// 初始管道终点
RequestDelegate pipelineEnd = async context => 
{
    context.Response.StatusCode = 404; // 默认404
};

// 应用我们的自定义中间件
var customMiddleware = generatedMiddleware(pipelineEnd);

// 最终生成的委托
RequestDelegate finalPipeline = async context =>
{
    var logger = // 获取logger实例;
    var middlewareInstance = new CustomLoggingMiddleware(pipelineEnd, logger);
    
    await middlewareInstance.InvokeAsync(context);
};
完整的转换流程图示
自定义中间件类
    ↓
UseMiddleware<T>() 扩展方法
    ↓
反射分析中间件类的构造函数和方法
    ↓
生成优化的委托代码 (Func<RequestDelegate, RequestDelegate>)
    ↓
注册到 IApplicationBuilder 的中间件列表
    ↓
管道构建时被包装成最终的 RequestDelegate
    ↓
请求到达时执行生成的委托代码

支持多种方法签名的中间件

ASP.NET Core 支持多种方法签名,转换器会智能处理:

方式 1: 标准的 InvokeAsync 方法

csharp

public async Task InvokeAsync(HttpContext context)
{
    await _next(context);
}

方式 2: 带服务的 InvokeAsync 方法

csharp

public async Task InvokeAsync(HttpContext context, ISomeService service)
{
    // 依赖注入会自动注入服务
    await _next(context);
}

方式 3: 同步的 Invoke 方法

csharp

public void Invoke(HttpContext context)
{
    // 同步处理
    _next(context).Wait();
}

实际的复杂中间件示例

csharp

public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;
    private readonly RateLimitOptions _options;

    // 构造函数注入
    public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache, IOptions<RateLimitOptions> options)
    {
        _next = next;
        _cache = cache;
        _options = options.Value;
    }

    // 方法参数注入
    public async Task InvokeAsync(HttpContext context, ILogger<RateLimitingMiddleware> logger)
    {
        var clientIp = context.Connection.RemoteIpAddress?.ToString();
        var cacheKey = $"rate_limit_{clientIp}";
        
        // 速率限制逻辑
        if (_cache.TryGetValue(cacheKey, out int requestCount) && requestCount >= _options.MaxRequests)
        {
            logger.LogWarning("IP {IP} 触发速率限制", clientIp);
            context.Response.StatusCode = 429;
            await context.Response.WriteAsync("Too Many Requests");
            return;
        }
        
        // 更新计数器
        _cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(1));
        
        await _next(context);
    }
}

public class RateLimitOptions
{
    public int MaxRequests { get; set; } = 100;
}

依赖注入的自动处理

转换过程中,依赖注入容器会自动处理:

csharp

// 转换器会分析构造函数和方法参数
public async Task InvokeAsync(
    HttpContext context, 
    ILogger<RateLimitingMiddleware> logger,        // 方法参数注入
    IMemoryCache cache,                            // 方法参数注入  
    IOptions<RateLimitOptions> options)            // 方法参数注入
{
    // 所有这些参数都会从DI容器自动解析
}

10. 总结:转换流程的关键步骤

  1. 类定义:创建带有 RequestDelegate 参数构造函数和 Invoke/InvokeAsync 方法的类
  2. 扩展方法注册:通过 UseMiddleware<T> 注册到管道
  3. 反射分析:框架分析中间件类的构造函数和方法签名
  4. 委托生成:生成优化的 Func<RequestDelegate, RequestDelegate> 委托
  5. 管道构建:在 Build() 方法中被包装到管道中
  6. 请求执行:每次请求时创建中间件实例并调用相应方法

这种设计的好处:

  • 类型安全:编译时检查中间件签名
  • 依赖注入:自动处理服务生命周期
  • 性能优化:使用代码生成而非纯反射
  • 灵活性:支持多种方法签名和参数注入方式
posted @ 2025-09-26 15:05  闪存第一搬运工  阅读(10)  评论(0)    收藏  举报