5.3 筛选器/拦截器/过滤器/AOP
5.3.1 关于筛选器
筛选器又名过滤器,拦截器,在 ASP.NET Core 中,可在请求处理管道中特定阶段之前或之后运行代码。筛选器是非常经典的面向切面编程方式,也就是所谓的 AOP 操作。
通俗点说就是可以在控制器 Action 执行前后进行切面操作或返回 Result 结果前后操作。
5.3.2 应用场景
通过自定义筛选器可以实现错误处理,缓存处理,授权处理,日志记录,实现工作单元事务(Uow)等等切面操作,从而使业务逻辑和系统行为逻辑进行分离。
5.3.2.1 筛选器优点
- 易拓展,易集成
- 业务和系统逻辑分离,不干扰 业务代码
- 可实现接口多维度控制,如请求参数篡改,返回值篡改,限流,分布式事务支持
- ...
5.3.3 支持拦截应用
Mvc/WebAPI控制器/ActionRazor Pages页面- 框架提供的
动态 WebAPI - 所有请求资源
- 全局异常
5.3.4 筛选器类型
5.3.4.1 接口类型
- 授权筛选器:该筛选器是最先运行,用于确定是否已针对请求为用户授权。 如果请求未获授权,授权筛选器可以让管道短路。
IAuthorizationFilterIAsyncAuthorizationFilterAuthorizeFilter
- 资源筛选器:授权后运行,如果需要是大部分请求管道短路,它将会很有用
IResourceFilterIAsyncResourceFilter
- 操作筛选器:在调用操作方法之前和之后运行代码,可以更改传递的参数,返回结果等,不可在
Razor Pages中使用。IActionFilterIAsyncActionFilter
- 异常筛选器:在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
IExceptionFilterIAsyncExceptionFilter
- 结果筛选器:在执行操作结果之前和之后立即运行代码,仅当操作方法成功执行时,它们才会运行。 对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。
IResultFilterIAsyncResultFilterIAlwaysRunResultFilter:该接口主要针对所有操作结果运行拦截,也就是即使IResourceFilter设置了Result仍会执行并获取最终的ResultIAsyncAlwaysRunResultFilter
- Razor Pages 筛选器:允许
Razor Pages在运行Razor页面处理程序前后运行代码,和操作筛选器类似,但它们不能应用单个页面处理程序方法。IPageFilterIAsyncPageFilter
5.3.4.2 特性 Attribute 类型
- 授权特性筛选器 (
Attribute+IAsyncAuthorizationFilter):同上接口类型 - 操作特性筛选器 (
ActionFilterAttribute):同上接口类型 - 异常特性筛选器 (
ExceptionFilterAttribute):同上接口类型 - 结果特性筛选器 (
ResultFilterAttribute):同上接口类型 - 服务特性筛选器 (
ServiceFilterAttribute):支持依赖注入的服务筛选器特性 - 类型特性筛选器 (
TypeFilterAttribute):不支持依赖注入但可以传入自定义构造函数参数 - 组合特性筛选器 (
Attribute+ 接口类型方式):可以通过派生自Attribute和 特定接口实现,如Attribute, IActionFilter
关于选择哪种类型的筛选器有一个小技巧,当你不需要全局筛选器的时候使用特性筛选器,否则使用接口类型筛选器。
另外尽可能的使用带 IAsync 开头的异步筛选器,这样无需分开多个方法,可在一个方法中操作,还能提高吞吐量。
筛选器接口的同步和异步版本任意实现一个,而不是同时实现。
运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。
如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。
5.3.5 筛选器注册
ASP.NET Core 提供了多种筛选器注册方式,通常情况下不同的注册方式执行顺序不同,服务类型注册最先执行,其次是 Mvc Filter 方式,最后是特性方式。相同的方式中又按照注册前后来决定执行顺序,先注册先执行。
同时也提供了 IOrderedFilter 接口重写执行顺序,其 Order 属性值越高的先执行。
5.3.5.1 在 Startup.cs 中注册
最常见的注册筛选器的方式就是在 Startup.cs 中注册,这种方式表示全局注册,应用所有控制器/Action
public void ConfigureServices(IServiceCollection services)
{
// Mvc 方式注册一,全局执行
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader", "Result filter added to MvcOptions.Filters")); // 手动创建实例,带构造参数
options.Filters.Add(typeof(MySampleActionFilter)); // 类型 Type 方式
options.Filters.Add(new SampleGlobalActionFilter()); // 手动创建实例,无构造参数
});
// Mvc 方式注册二,全局执行
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<TFilter>();
});
// Mvc 注册方式三,全局执行,Furion 框架提供方式
services.AddMvcFilter<TFilter>();
}
5.3.5.2 特性方式注册
如果筛选器派生自 特性,则可通过特性方式注册,这种方式表示局部注册,只作用于特定的控制器/Action
- 直接贴方式
// 定义结果特性筛选器
public class AddHeaderAttribute : ResultFilterAttribute
{
// ...
}
// 直接贴方式,对于动态 WebAPI 也是一样的
[AddHeader]
public class SampleController : Controller
{
}
- 通过
[ServiceFilter]方式
这种方式适用于自定义的特性筛选器包含构造函数注入服务应用场景,这种方式必须在 ConfigureService 中通过 services.AddScoped<TFilter> 注册。
public class MyActionFilterAttribute : ActionFilterAttribute
{
// 注入服务
private readonly PositionOptions _settings;
public MyActionFilterAttribute(IOptions<PositionOptions> options)
{
}
}
需先在 Startup.cs 中注册筛选器
services.AddScoped<MyActionFilterAttribute>();
使用:
public class SampleController : Controller
{
// 通过 [ServiceFilter] 方式
[ServiceFilter(typeof(MyActionFilterAttribute))]
public IActionResult Index2()
{
// ...
}
}
- 通过
[TypeFilter]方式
[TypeFilter] 和 [ServiceFilter] 类似,唯一的区别就是 [TypeFilter] 不支持构造函数注入服务,但可以传递基元类型构造函数参数。
public class MyLogFilterAttribute : ActionFilterAttribute
{
// 构造函数包含基元类型参数
public MyLogFilterAttribute(string message, int level)
{
}
}
public class SampleController : Controller
{
// 通过 [TypeFilter] 方式
[TypeFilter(typeof(MyLogFilterAttribute), Arguments = new object[] { "Message", 10 })]
public IActionResult Index2()
{
// ...
}
}
5.3.6 授权筛选器
通过授权筛选器可以实现在所有请求到达控制器/Action 之前进行验证,如果授权失败,直接跳转到登录或者返回 401。
5.3.6.1 接口定义方式
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebApplication4.Filters;
/// <summary>
/// 自定义授权筛选器
/// </summary>
public class MyAuthorizationFilter : IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
Console.WriteLine("授权检查......");
// 获取控制器信息
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
// 获取控制器类型
var controllerType = actionDescriptor!.ControllerTypeInfo;
// 获取 Action 类型
var methodType = actionDescriptor.MethodInfo;
// 是否匿名访问
var allowAnonymouse = context.Filters.Any(u => u is IAllowAnonymousFilter)
|| controllerType.IsDefined(typeof(AllowAnonymousAttribute), true)
|| methodType.IsDefined(typeof(AllowAnonymousAttribute), true);
// 不是匿名才处理权限检查
if (!allowAnonymouse)
{
Console.Write("逻辑检查~~~~");
// 在 MVC 项目中,如果检查失败,则跳转到登录页
if (typeof(Controller).IsAssignableFrom(controllerType.AsType()))
{
context.Result = new RedirectResult("~/Login");
}
// WebAPI 或者其他类型
else
{
// 返回未授权
context.Result = new UnauthorizedResult();
}
}
// 否则直接跳过处理
else await Task.CompletedTask;
}
}
全局注册
在 ConfigureService 中注册,该方式会作用所有的控制器/ Action:
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<MyAuthorizationFilter>();
});
Furion 框架中提供了 services.AddMvcFilter<MyAuthorizationFilter>(),可使用它代替上面多行注册。
局部特性方式
[TypeFilter]:
[TypeFilter(typeof(MyAuthorizationFilter))]
public IActionResult Get()
{
// ...
}
[ServiceFilter]
services.AddScoped<MyAuthorizationFilter>();
[ServiceFilter(typeof(MyAuthorizationFilter))]
public IActionResult Get()
{
// ...
}
5.3.6.2 特性定义方式(组合)
只需要上述接口方式中添加派生 Attribute 并设置 [AttributeUsage] 接口,如:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
public class MyAuthorizationFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// 代码同上
}
}
使用:
[MyAuthorizationFilter]
public IActionResult Get()
{
// ...
}
5.3.7 资源筛选器
资源筛选器使用频率较少,通常用来处理资源缓存或者阻止模型(值)绑定操作。
