ASP.NET Core – Filter
介绍
Filter 类似 Middleware,只是它集中在处理 request 的前后,
站 MVC 角度看就是 before 和 after action,
站 Razor Pages 角度就是 before PageModel after
参考
Docs – Filters in ASP.NET Core(MVC Filter)
Docs – Filter methods for Razor Pages in ASP.NET Core(Razor Pages Filter)
Razor Pages – Filter
IPageFilter & IAsyncPageFilter
先讲 Razor Pages 的 Filter 吧。
IPageFilter 和 IAsyncPageFilter 这两个是 Razor Pages 主要的 Filter,两个拦截的点是一样的,只是一个 for sync 一个 for async。
public class MyPageFilter : IPageFilter { public void OnPageHandlerSelected(PageHandlerSelectedContext context) { // 1. before PageModel.OnGet } public void OnPageHandlerExecuting(PageHandlerExecutingContext context) { // 2. before PageModel.OnGet } public void OnPageHandlerExecuted(PageHandlerExecutedContext context) { // 3. after PageModel.OnGet, but before View } }
它有 3 个点可以拦截,主要就是 PageModel.OnGet 之前和之后。context 可以拿到很多资料,比如 Request,当前 PageModel 的 class Type 等等。
再看 Async 的。
public class MyAsyncPageFilter : IAsyncPageFilter { public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) { // 1. before PageModel.OnGet return Task.CompletedTask; } public Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) { // 2. before PageModel.OnGet return next.Invoke(); // 3. after PageModel.OnGet, but before View } }
虽然它少了一个 overload method,但同样有 3 个拦截点。所以 IPageFilter,IAsyncPageFilter 本质是一样的,只是看我们拦截后是否有需要异步来做选择就可以了。
Apply Global Filter
定义好 IPageFilter 后,我们还得 apply 它。到 program.cs 把 filter 添加进去就可以了。
builder.Services.AddRazorPages().AddMvcOptions(options => { options.Filters.Add(new MyPageFilter()); options.Filters.Add(new MyAsyncPageFilter()); });
注: 它虽然是 Razor Pages Filter 但却是添加到 MvcOptions 里头哦。
注: 执行顺序和我们 apply filter 的顺序是有关系的哦。
如果我们先 Add AsyncFilter 顺序就不是上面这样了,但我认为我们不应该依赖这个顺序去做逻辑啦,不然会很管理的,不顺风水。
Dependancy Injection in Filter
上面我们 apply filter 的时候是用实例化 new MyPageFilter() 这种方式。所以它不支持 DI。
若需要 DI,我们得这样。
builder.Services.TryAddScoped<MyService>(); builder.Services.AddRazorPages().AddMvcOptions(options => { options.Filters.Add(typeof(MyPageFilter)); options.Filters.Add(typeof(MyAsyncPageFilter)); });
把实例化 new MyPageFilter() 改成 typeof(MyPageFilter)。
接着就能依赖注入了。
注:typeof(MyPageFilter) 是 scope level,每一个请求都会重新实例化一个 MyPageFilter。而 new MyPageFilter() 则始终是一个对象。
ServiceFilterAttribute
通常 Filter 需要 DI,我们会更倾向于使用 ServiceFilterAttribute。
builder.Services.TryAddScoped<MyPageFilter>(); builder.Services.AddRazorPages().AddMvcOptions(options => { options.Filters.Add(new ServiceFilterAttribute(typeof(MyPageFilter)) { IsReusable = true }); });
首先把 MyPageFilter 添加进 DI。然后添加 Filter。
它多了一个配置 IsReusable,IsReusable 可以让 Filter 变成单列模式。这样就不需要每一次请求都重新实例化 Filter 了。
ServiceFilterAttribute 不只这一个功能,我们继续往下看。
Filter on Specify Page
上面我们是 apply global,每一个 page 都会被这个 Filter 拦截。
如果我们只想拦截某一些 pages,我们也可以利用 ServiceFilterAttribute。
builder.Services.TryAddScoped<MyPageFilter>();
builder.Services.AddRazorPages();
我们依然需要把 MyPageFilter 添加到 DI,但是不需要添加 Filter 了。
取而代之的是在想要拦截的 PageModel 上添加 Attribute。
[ServiceFilter<MyPageFilter>(IsReusable = true)] public class IndexModel : PageModel {}
TypeFilterAttribute
既然已经介绍了 ServiceFilterAttribute 那就顺便介绍 TypeFilter。
它和 ServiceFilterAttribute 非常像,区别是它可以在声明 Attribute 时传参数。
假设我们的 MyPageFilter 需要一个参数 value,这个参数和 _myService 不同,它不来自 DI。
在声明 Attribute 时,传入参数。
[TypeFilter<MyPageFilter>(Arguments = ["value"], IsReusable = true)] public class IndexModel : PageModel {}
注意:
TypeFilter 不是直接通过 DI 来创建的,所以 MyPageFilter 不需要,也不可以添加到 DI。
IResultFilter & IAsyncResultFilter
IPageFilter 拦截的是 PageModel.OnGet 前后,而且是 before View。
IResultFilter 则是拦截 View 前后。它的用法和 IPageFilter 完全一样,只是拦截的点不同而已。
public class MyResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { // 1. before View, but after IPageFilter.OnPageHandlerExecuted } public void OnResultExecuted(ResultExecutedContext context) { // 4. after view } } public class MyAsyncResultFilter : IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // 2. before View await next.Invoke(); // running View // 3. after View } }
apply 的方式是一样的
builder.Services.AddRazorPages().AddMvcOptions(options => { options.Filters.Add(new MyResultFilter()); options.Filters.Add(new MyAsyncResultFilter()); });
ResultFilterAttribute
ResultFilterAttribute 底层是 IResultFilter,只是 ASP.NET Core wrap 了一层 Attribute 而已。
好处就是可以直接指定 apply to specify page。不过如果需要 DI 的话,那依然要 wrap Service/TypeFilterAttribute 哦。
ResultFilterAttribute 同时实现了 IResultFilter 和 IAsyncResultFilter 的拦截点。
public class MyResultFilter : ResultFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { // before base.OnResultExecuting(context); // before } public override void OnResultExecuted(ResultExecutedContext context) { // after base.OnResultExecuted(context); // after } public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // before await base.OnResultExecutionAsync(context, next); // before > View > after // after } }
不需要去 program.cs 做 apply,只要把 Attribute apply 到 PageModel 就可以了。
[MyResultFilter] public class IndexModel : PageModel {}
Mvc – Filter
ActionFilterAttribute
它相等于 IPageFilter + IResultFilter + wrap attribute。
public class MyActionFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { // 2. before action base.OnActionExecuting(context); // running action } public override void OnActionExecuted(ActionExecutedContext context) { // 3. after action base.OnActionExecuted(context); } public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 1. before action return base.OnActionExecutionAsync(context, next); // 4. after action but before View } public override void OnResultExecuting(ResultExecutingContext context) { // 6. before View base.OnResultExecuting(context); // running View } public override void OnResultExecuted(ResultExecutedContext context) { // 7. after View base.OnResultExecuted(context); } public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // 5. before View return base.OnResultExecutionAsync(context, next); // 8. after View } }
同样的,如果需要 DI 任然要 wrap Service/TypeFilterAttribute 哦。
总结
1. Filter 有 sync 和 async 两个版本。
比如:IPageFilter vs IAsyncPageFilter
它们接口可能有微微不同,但可拦截地方是一样多的。
2. 拦截点
before PageModel / Controller
after PageModel / Controller
before View
after View
3. Razor Pages 需要 IPageFilter + IResultFilter 才能拦截完所有地方。Mvc 只要一个 ActionFilter 就可以了。
4. IPageFilter、IResultFilter、IActionFilter 都是底层接口。必须 apply to global。
5. ResultFilterAttribute、ActionFilterAttribute 是 Attribute 版,可以 apply to global 也可以选择只 apply to specify PageModel 或 Controller,没有 PageFilterAttribute 的哦。
6. IPageFilter、IResultFilter、IActionFilter 如果要 DI,可以在 apply to global 时使用 typeof(),取代实例化。
7. FilterAttribute 要 DI 的话,需要使用 ServiceFilterAttribute,记得把 FilterAttribute 添加到 DI 中。
8. FilterAttribute 要参数的话,需要使用 TypeFilterAttribute,不要把 FilterAttribute 添加到 DI 中,会报错的,不用担心,它本来就支持 DI 了。