ASP.NET Core 3.1中Middleware和Filter用法实践
前言
在我们用ASP.NET Core进行开发的过程中经常使用到两种AOP技术中间件(Middleware)和过滤器(Filter),很多时候两个组件有很多相似的用法,具体什么场景应用什么组件其实是很模糊的,下面我通过自己的实践和举例来进行讲解。
组件介绍
具体Middleware和Filter各自怎么用,已经有很多文章和Demo参考,今天我们来说一下,Filter和Middleware在Request请求中的顺序问题,在微软官网文档中在对Filters介绍之前有这样一段话
Filters run within the ASP.NET Core action invocation pipeline, sometimes referred to as the filter pipeline. The filter pipeline runs after ASP.NET Core selects the action to execute.
还有一张图示如下:
通过该段说明和图示我们可以知道,Request首先是经过系统或我们自定义的Middleware之后经过Action Selection,然后才会进入Filter Pipeline。
实践举例
下面来提供一个Middleware和Filter联合使用的场景,分别用到TraceIdMiddleware,ExceptionMiddleware和TokenFilter
TraceIdMiddleware主要用于拦截Request,检查是否带有traceid这个Header,如果没有就添加一个traceid的header到Request请求中,代码如下:
public class TraceIdMiddleware
{
private readonly RequestDelegate _next;
public TraceIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string traceid = null;
if (context.Request.Headers.TryGetValue("traceid", out var htraceId))
{
traceid = htraceId.ToString();
}
if (string.IsNullOrEmpty(traceid))
{
traceid = Guid.NewGuid().ToString();
context.Request.Headers.Add("traceid", traceid);
}
await _next.Invoke(context);
}
}
public static class TraceIdMiddlewareExtensions
{
public static IApplicationBuilder UseTraceId(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TraceIdMiddleware>();
}
}
ExceptionMiddleware为全局捕获异常中间件,用于全局拦截我们代码中未catch的异常,返回统一格式。代码如下:
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception e)
{
await HandleException(context, e);
}
}
private async Task HandleException(HttpContext context, Exception e)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/json;charset=utf-8;";
_logger.LogError(e.Message);
string error = JsonSerializer.Serialize(new { Code = 500, Message = e.Message });
await context.Response.WriteAsync(error);
}
}
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseException(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionMiddleware>();
}
}
TokenFilter实现了接口,获取请求中的token并验证其合法性。具体代码如下:
public class TokenFilter : Attribute, IAuthorizationFilter
{
private readonly ILogger<TokenFilter> _logger;
public TokenFilter(ILogger<TokenFilter> logger)
{
_logger = logger;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
string token = null;
if (context.HttpContext.Request.Headers.TryGetValue("token", out var utoken))
{
userToken = utoken.ToString();
}
if (string.IsNullOrEmpty(userToken))
{
var traceId = context.HttpContext.Request.Headers["traceid"].ToString();
_logger.LogInformation($"traceId:{traceId},Error:认证失败");
throw new Exception("认证失败");
}
// something else
}
}
三个组件执行时序图如下:
Requset请求首先进入TraceIdMiddleware,检测是否有traceid的header,没有则赋值,然后经过ExceptionMiddleware不对Request进行处理,在TokenFilter中验证token,如果token无效则记录日志并抛出异常,日志中包含header中的traceid,在返回路径中ExceptionMiddleware将会捕获返回值中的Exception,统一返回格式并返回。
总结
通过以上的例子我们可以总结出两点:
- Middleware执行于Action Selection之前,所以我们应该将一些业务无关的功能放在Middleware中,而将一些业务相关的内容放在Filter中来处理。
- 在同时使用Middleware和Filter时,一定要注意使用顺序,以免出现不必要的异常。