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 的顺序是有关系的哦。

View Code

如果我们先 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 了。

 

posted @ 2021-10-23 17:45  兴杰  阅读(161)  评论(0编辑  收藏  举报