目录
一、什么是筛选器
筛选器(filter,也可以翻译为“过滤器") 是 ASP.NET Core中提供的一种切面编程机制,它允许开发人员创建自定义筛选器来处理横切关注点,也就是在ASP.NET Core特定的位置执行我们自定义的代码,比如在控制器的操作方法之前执行数据检查的代码,或者在 ActionResult执行的时候向响应报文头中写入自定义数据等。
ASP.NET Core 中的筛选器有以下5种类型:授权筛选器、资源筛选器、操作筛选器、异常筛选器和结果筛选器。在进行项目开发的时候,我们一般配置授权策略或编写自定义授权策略,而不是编写自定义授权筛选器,只有在需要自定义授权框架时才会用到自定义授权筛选器。类似的道理也适用于资源筛选器和结果筛选器,因此重点讲解异常筛选器和操作筛选器。
所有筛选器一般有同步和异步两个版本,比如同步操作筛选器实现IActionFilter接口,而异步操作筛选器实现 IAsyncActionFilter 接口。在大部分场景下,异步筛选器的性能更好,而且可以支持在实现类中编写异步调用的代码,因此主要了解异步筛选器。
二、异常筛选器
当系统中出现未经处理的异常的时候,异常筛选器就会执行,我们可以在异常筛选器中对异常进行处理。
我们知道,在 ASP.NET Core Web API中,如果程序中出现未处理异常,就会生成响应报文。
这样的异常信息只有客户端才知道,网站的运维人员和开发人员不知道这个异常的存在,我们需要在程序中把未处理异常记录到日志中。为了规范化接口的格式,当系统中出现未处理异常的时候,我们需要统一给客户端返回如下格式的响应报文,{"code ":" 500" ,"message":"异常信息"}。如果程序是在开发阶段运行,则异常信息的内容为全部异常堆栈,否刷异常信息的内容固定为程序中出现未处理异常。
下面我们实现自定义异常筛选器的两个功能:
public class MyExceptionFilter : IAsyncExceptionFilter{
private readonly ILogger<MyExceptionFilter> logger;
private readonly IHostEnvironment env;
public MyExceptionFilter(ILogger<MyExceptionFilter> logger,IHostEnvironment env)
{
this.logger = logger;
this.env = env;
}
public Task OnExceptionAsync(ExceptionContext context)
{
Exception exception = context.Exception;
logging.LogError(exception,"UnhandledException occured");
}
string message;
if(env.IsDevelopment ())
message =exception.ToString();
else
message ="程序中出现未处理异常";
ObjectResult result = new ObjectResult(new { code = 500,message = message });
result.StatusCode = 500;
context.Result = result;
context.ExceptionHandled = true;
return Task.CompletedTask;
}}
异步异常筛选器要实现IAsyncExceptionFilter 接口。由于筛选器中需要把异常信息记录到日志中并且判断程序的执行环境,因此筛选器需要注入ILogger和 IHostEnvironment 这两个服务。在第9行代码中,我们使用context.Exception获取异常对象,然后在第10行代码中,把异常写入日志。在第11~16行代码中,我们检测程序的运行环境来决定mesSage的值中是否显示异常堆栈。很显然,在生产环境中,我们不能是示异常堆栈。以避免泄露程序的机密信息。在第17~19行代码中,我们设置响应报文的内容。在10行中,我们设詈ontext.ExcentionHandled的值为true,通过这样的方式来告知ASP.NET Core 不再执行默认的异常响应逻辑。
然后,我们在Program.cs的builder.Build之前添加如下代码,设置全局的筛选器。
builder.servies.Configure<MvcOptions> (options =>
{
options.Filters.Add<MyExceptionFilter>();
});
MvcOptions是 ASP.NET Core项目的主要配置对象,我们在第3行代码中向Filters注册全局的筛选器,这样,项目中所有的ASP.NET Core中的未处理异常都会被MyExceptionFilter 处理。用这种方式注入的筛选器是由依赖注入机制进行管理的,因此我们可以通过构造方法为筛选器注入其他的服务。
大家如果在网上看到筛选器,可能会看到在AddMVC中注册筛选器的代码,那是旧版ASP.NET Core中的写法,在最新版的ASP.NET Core中,直接对MvcOptions进行配置就可以。
如上设置后,当控制器中的 Action出现未处理异常时,就会出现响应报文。
需要注意的是,只有ASP.NET Core线程中的未处理异常才会被异常筛选器处理,后台线程中的异常不会被异常筛选器处理。
三、操作筛选器基础
每次 ASP.NET Core 中控制器的操作方法执行的时候,操作筛选器都会被执行,我们可以在操作方法执行之前和执行之后执行一些代码,完成特定的功能。
操作筛选器一般实现IAsyncActionFilter 接口,这个接口中定义了OnActionExecutionAsync
方法,方法的声明如下所示。
Task OnActionExecutionAsync (ActionExecutingContext context,ActionExecutionDelegate next)
其中,context参数代表 Action执行的上下文对象,从context中我们可以获取请求的路径、参数值等信息;next参数代表下一个要执行的操作筛选器。一个项目中可以注册多个操作筛选器,这些操作筛选器组成一个链,上一个筛选器执行完了再执行下个。next就是一个用来指向下一个操作筛选器的委托,如果当前的操作筛选器是最后一个筛选器的话,next 就会执行要执行的操作方法。
下面来编写两个操作筛选器,以便演示操作筛选器的用法。
第1步,编写一个实现了IAsyncActionFilter 接口的类 MvActionFilterl,如代码如下所示。
public class MyActionFilterl: IAsyncActionFilter
{
public async Task OnActionEXecutlonAsync (AccionEAecucin9oncext context,
ActionExecutionDelegate next)
{
Console.writeLine ("MyActionFilter 1:开始执行");
ActionExecutedContext r = await next();
if(r.Exception != null)
Console.writeLine("MyActionFilter 1:执行失败");
else
Console.WriteLine("MyActionFilter 1:执行成功");
}
}
第7行代码用next来执行下一个操作筛选器,如果这是最后一个操作筛选器,它就会执行实际的操作方法。next 之前的代码是在操作方的代码而next之后的代码则是在操作方法执行之后要执行的代码。
next的返回值是操作方法的执行结果,返回值是 ActionExecutedContext类型的,如果操作方法执行的时候出现了未处理异常,那么ActionExecutedContext的Exception属性就是异常对象,ActionExecutedContext的属性就是操作方法的执行结果。
第二步,编写一个和MyActionFilter1类似的类MyActionFilter2,如下所示:
public class MyActionFilter2 :IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
Console.WriteLine ("MyActionFilter 2:开始执行");
ActionExecutedContext r = await next();
if(r.Exception != null)
Console.WriteLine("MyActionEFilter 2:执行失败");
else
Console.WriteLine("MyActionFilter 2:执行成功");
}}
第3步,在program.cs中注册这两个掘作洗器,如代码下所示:
builder.Services.Configure<Mvc0ptions> (options =>
{
options.Filters.Add<MyActionFilter1>();
options.Filters.Add<MyActionFilter2>();
});
第4步,在控制器中增加一个要测试的操作方法,如代码如下:
[HttpGet]
public string GetData()
{
Console.WriteLine("执行GetData");
return "yzk" ;
}
第5步,启动项目,访问操作方法的路径。
从程序运行结果可以看出,多个操作筛选器和操作方法的执行顺序和我们的分析结果一致。需要特别说明的是,虽然操作筛选器实现的是 IAsyncActionFilter接口,但是并不是说操作筛选器只能处理同步操作方法。无论是同步的操作筛选器还是异步的操作筛选器,都可以处理同步和异步的操作方法,区别在于操作筛选器的实现代码是同步代码还是异步代码。