一、什么是中间件?
-
中间件 是一种装配到 ASP.NET Core 应用程序请求处理管道中的软件组件,用于处理 HTTP 请求和响应。
-
每个中间件组件可以:
-
选择是否将请求传递到下一个中间件:通过调用
next()
或者不调用next()
来决定是否将请求继续传递给下一个中间件。 -
在传递前后执行某些操作:可以在将请求传递给下一个中间件之前或者之后执行一些自定义逻辑。
-
请求委托用于生成请求管道。 请求委托处理每个
HTTP
请求。
-
-
中间件的作用:中间件比筛选器更底层和更上游,是一种性能更高、适用范围更广的面向切面技术,可处理如网关、URL 转发、限流等复杂操作。
二、常见的中间件
1. 所有请求返回同一个结果
- 这个中间件会对所有请求返回相同的响应内容,不论请求的 URL 是什么。
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!"); // 所有请求的响应内容
});
2. 拦截所有请求
- 通过
app.Use
方法可以拦截所有请求,并在处理请求时进行一些操作,例如设置统一的 HTTP 头信息。
app.Use(async (context, next) =>
{
context.Response.Headers["framework"] = "Furion"; // 设置自定义响应头
await next.Invoke(); // 将请求传递给下一个中间件
});
3. 特定路由中间件
- 这个中间件仅对特定的 URL 路径进行处理,例如
/hello
。
app.Map("/hello", app => {
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1"); // 仅对 /hello 路径进行处理
});
});
4. 嵌套路由中间件
- 中间件可以嵌套使用,根据不同的路由层级进行处理,例如处理
/level1/level2a
和/level1/level2b
路径的请求。
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
level2AApp.Run(async context =>
{
await context.Response.WriteAsync("Map Level 2A");
});
});
level1App.Map("/level2b", level2BApp => {
level2BApp.Run(async context =>
{
await context.Response.WriteAsync("Map Level 2B");
});
});
});
三、自定义中间件
1. 通过 app.Use
定义中间件
- 这是定义中间件的最简单方式,但通常不推荐用于复杂场景。可以直接在
app.Use
内部编写中间件逻辑。
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"]; // 获取请求中的 culture 查询参数
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture; // 设置当前文化信息
CultureInfo.CurrentUICulture = culture; // 设置当前 UI 文化信息
}
await next(context); // 将请求传递给下一个中间件
});
2. 通过独立类定义中间件(推荐)
- 创建一个独立的中间件类,并通过
app.UseMiddleware
方法来使用这个中间件。这种方式便于复用和维护。
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; // 设置当前线程的 UI 文化信息
}
await _next(context); // 将请求传递给下一个中间件
}
}
// 扩展方法
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>(); // 使用自定义中间件
}
}
// 使用中间件
app.UseRequestCulture();
3. 配置更多参数
- 中间件可以接收更多的构造参数,包括依赖注入的服务和普通参数。
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("Executing middleware for {Name} who is {Age} years old.", name, age); // 使用日志记录操作信息
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
解析服务。这样可以在中间件中使用已经注册的服务。
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)
{
var repository = context.RequestServices.GetService<IRepository>(); // 解析服务
await _next(context); // 继续执行下一个中间件
}
}
六、删除特定的 HTTP 响应头
- 可以通过中间件删除或修改 HTTP 响应头。例如,删除默认添加的响应头信息。
app.Use(async (context, next) =>
{
context.Response.Headers.Remove("Server"); // 删除 Server 响应头
await next(); // 继续执行下一个中间件
});
七、常见问题
- 在中间件中获取终点路由的特性或其他信息时,可以通过以下方式实现:
var endpointFeature = context.Features.Get<IEndpointFeature>(); // 获取终点特性
var attribute = endpointFeature?.Endpoint?.Metadata?.GetMetadata<YourAttribute>(); // 获取自定义的特性
- 需要注意的是,这种操作必须在
UseRouting()
和UseEndpoints()
之间的中间件中调用,才能确保路由信息已经解析。