中间件使用指南 #

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

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

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

中间件更多内容

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

常见中间件 #

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

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

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

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

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

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

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

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

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

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

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 (opens new window)

自定义中间件 #

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

app.Use 方式 (不推荐) #

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

独立类 方式(推荐) #

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

  • 定义中间件,建议以 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();
    // ... 其他中间件
}

配置更多参数 #

默认情况下,自定义独立类中间件构造函数只有一个 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, "百小僧");

中间件顺序 #

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

依赖注入/解析服务 #

中间件有两种方式注入服务,一种是通过构造函数注入,一种是通过 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)
    {
        // 其他代码

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

特别说明

通过构造函数的方式建议注入 单例模式 的服务,否则可能存在服务不能释放问题,如需使用瞬时或作用域的服务,可使用下列 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)
    {
        // 非单例模式
        using var serviceProvider = context.RequestServices.CreateScope();
        var repository = serviceProvider.ServiceProvider.GetService<IRepository>();

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

常见问题 #

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

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

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

注意事项

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

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

了解更多 #

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

上次更新: 3/10/2023, 5:09:25 PM