Skip to content

5.5 中间件 (Middleware)

5.5.1 关于中间件

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。
  • 请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

一句话总结:中间件是比筛选器更底层,更上游的面向切面技术,其性能最高,可处理的应用范围远比过滤器广,如实现网关,URL 转发,限流等等。

中间件更多内容本章节暂不考虑将中间件展开讲,想了解更多知识可阅读官方文档 【ASP.NET Core - 中间件

5.5.2 常见中间件

5.5.2.1 所有请求返回同一个结果

app.Run(async context =>  
{  
    await context.Response.WriteAsync("Hello world!");  
});  

5.5.2.2 拦截所有请求(可多个)

app.Use(async (context, next) =>  
{  
    // 比如设置统一头  
    context.Response.Headers["framework"] = "Furion";  

    // 执行下一个中间件  
    await next.Invoke();  
});  

// 多个  
app.Use(...);  

5.5.2.3 特定路由中间件(可多个)

app.Map("/hello", app => {  
    app.Run(async context =>  
    {  
        await context.Response.WriteAsync("Map Test 1");  
    });  
});  

app.Map("/hello/say", app => {  
    // ....  
});  

5.5.2.4 嵌套路由中间件(可多个)

app.Map("/level1", level1App => {  
    level1App.Map("/level2a", level2AApp => {  
        // "/level1/level2a" processing  
    });  
    level1App.Map("/level2b", level2BApp => {  
        // "/level1/level2b" processing  
    });  
});  

更多例子查看官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view\=aspnetcore-6.0

5.5.3 自定义中间件

自定义中间件有多种方式,最简单的是通过 app.Use 方式,另外还支持独立类定义方式。

5.5.3.1 app.Use 方式 (不推荐)

Starup.cs

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;  
    }  

    // 调用下一个中间件  
    await next(context);  
});  

5.5.3.2 独立类 方式(推荐)

独立类的方式是目前最为推荐的方式,拓展性强,维护性高,如:

  • 定义中间件,建议以 Middleware 结尾:
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;  
        }  

        // 调用下一个中间件  
        await _next(context);  
    }  
}  

  • 添加中间件拓展类

定义了中间件之后,需要创建这个中间件的拓展类,中间件拓展方法建议以 Use 开头,如:

public static class RequestCultureMiddlewareExtensions  
{  
    public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)  
    {  
        return builder.UseMiddleware<RequestCultureMiddleware>();  
    }  
}  

  • Startup.cs 中使用
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
    // ... 其他中间件  
    app.UseRequestCulture();  
    // ... 其他中间件  
}  

5.5.3.3 配置更多参数

默认情况下,自定义独立类中间件构造函数只有一个 RequestDelegate 参数,除此之后,还可以注入服务接口/类,另外还支持传入任何其他类型。

  • 服务类型参数
using System.Globalization;  

namespace Middleware.Example;  

public class RequestCultureMiddleware  
{  
    private readonly RequestDelegate _next;  
    private readonly ILogger<RequestCultureMiddleware> _logger;  

    public RequestCultureMiddleware(RequestDelegate next  
        , ILogger<RequestCultureMiddleware> logger)  
    {  
        _next = next;  
        _logger = logger;  
    }  

    public async Task InvokeAsync(HttpContext context)  
    {  
        // 其他代码  

        _logger.LogInformation("...");  

        // 调用下一个中间件  
        await _next(context);  
    }  
}  

  • 非服务类型参数

除此之外,还可以添加 非服务参数 参数,但必须是声明在服务参数后。

using System.Globalization;  

namespace Middleware.Example;  

public class RequestCultureMiddleware  
{  
    private readonly RequestDelegate _next;  
    private readonly ILogger<RequestCultureMiddleware> _logger;  

    public RequestCultureMiddleware(RequestDelegate next  
        , ILogger<RequestCultureMiddleware> logger  
        , int age  
        , string name)  
    {  
        _next = next;  
        _logger = logger;  
    }  

    public async Task InvokeAsync(HttpContext context)  
    {  
        // 其他代码  

        _logger.LogInformation("...");  

        // 调用下一个中间件  
        await _next(context);  
    }  
}  

之后还需要修改中间件拓展类:

public static class RequestCultureMiddlewareExtensions  
{  
    public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder, int age, string name)  
    {  
        return builder.UseMiddleware<RequestCultureMiddleware>(new object[] {age, name });  
    }  
}  

使用:

app.UseRequestCulture(30, "百小僧");  

5.5.4 中间件顺序

中间件是有执行顺序的,而且是先注册的先执行,无法通过其他方式更改,参考下图:

5.5.5 依赖注入/解析服务

中间件有两种方式注入服务,一种是通过构造函数注入,一种是通过 httpContext.RequestServices 方式解析。

5.5.5.1 构造函数方式

using System.Globalization;  

namespace Middleware.Example;  

public class RequestCultureMiddleware  
{  
    private readonly RequestDelegate _next;  
    private readonly ILogger<RequestCultureMiddleware> _logger;  

    public RequestCultureMiddleware(RequestDelegate next  
        , ILogger<RequestCultureMiddleware> logger  
        , IHostEnvironment hostEnvironment)  
    {  
        _next = next;  
        _logger = logger;  
    }  

    public async Task InvokeAsync(HttpContext context)  
    {  
        // 其他代码  

        // 调用下一个中间件  
        await _next(context);  
    }  
}  

5.5.5.2 httpContext.RequestServices 方式

HttpContext 提供了 RequestServices 属性方便解析服务。

using System.Globalization;  

namespace Middleware.Example;  

public class RequestCultureMiddleware  
{  
    private readonly RequestDelegate _next;  
    private readonly ILogger<RequestCultureMiddleware> _logger;  

    // 构造函数注册  
    public RequestCultureMiddleware(RequestDelegate next  
        , ILogger<RequestCultureMiddleware> logger  
        , IHostEnvironment hostEnvironment)  
    {  
        _next = next;  
        _logger = logger;  
    }  

    public async Task InvokeAsync(HttpContext context)  
    {  
        // 通过 context.RequestServices 解析  
        var repository = context.RequestServices.GetService<IRepository>();  

        // 调用下一个中间件  
        await _next(context);  
    }  
}  

5.5.6 删除特定的 HTTP 响应头

默认情况下,Furion 框架会输出当前的运行环境和 Furion 版本信息,如无需显示,可在 Startup.cs 中移除即可。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
    if (env.IsDevelopment())  
    {  
        app.UseDeveloperExceptionPage();  
    }  

    app.Use(async (context, next) =>  
    {  
        context.Response.Headers.Remove("furion");  
        context.Response.Headers.Remove("environment");  

        await next();  
    });  

    // .... 其他代码  
}  

5.5.7 常见问题

由于中间件是比较原始的切面方式,有时候我们需要获取终点路由的特性或者其他信息,则需要一点技巧:

// 获取终点路由特性  
var endpointFeature = context.Features.Get<IEndpointFeature>();  

// 获取是否定义了特性  
var attribute = endpointFeature?.Endpoint?.Metadata?.GetMetadata<YourAttribute>()  

注意事项要想上面操作有效,也就是不为 null,需要满足以下条件,否则 endpointFeature 返回 null

  • 启用端点路由 AddControllers() 而不是 AddMvc()
  • UseRouting()UseEndpoints() 之间调用你的中间件

5.5.8 了解更多

想了解更多中间件知识可阅读官方文档 【ASP.NET Core - 中间件

5.5.9 反馈与建议

与我们交流给 Furion 提 Issue