在Asp.Net Core中使用ModelConvention实现全局过滤器隔离

2020-01-18 19:39:22王旭

从接口摘要可以看到,这个接口允许自定义 ControllerModel 对象,而如何自定义内容正是通过 Apply 方法来实现,这个方法提供了当前 ControllerModel 对象的实例,我们可以在它身上获取到的东西实在太多了,看看它包含些什么:

有了这些,我们可以做很多很灵活的操作,例如通过设置 ControllerName 字段强制更改控制器的名称让程序中写死的控制器名失效,也可以通过 Filters 字段动态更新它的过滤器集合,通过 RouteValues 来更改路由规则等等。

说到这里,很多人会觉得这玩意儿和自定义过滤器看起来差不多,最开始我也这么认为,但经过实际代码调试我发现它的生命周期要比过滤器早的多,或者说根本无法比较, 这个家伙只需要在应用启动时执行一次并不用随着每次请求而执行 。也就是说,它的执行时间比激活控制器还要早,那时候根本没有过滤器什么事儿,它的调用是发生在 app.UseEndpoints()

回到最开始的需求。基于上面的介绍,我们可以自定义如下的约定:


 public class ApiControllerAuthorizeConvention : IControllerModelConvention
 {
  public void Apply(ControllerModel controller)
  {
   if (controller.Filters.Any(x => x is ApiControllerAttribute) && !controller.Filters.Any(x => x is AccessControlFilter))
   {
    controller.Filters.Add(new AccessControlAttribute());
   }
  }
 }

上面的主要思路就是通过判断控制器本身的过滤器集合是否包含 ApiControllerAttribute 来识别是否API Controller,如果是API Controller并且没有标记过 AccessControlAttribute 的话就新建一个实例加入进去。

那么如何把这个约定注册到应用中呢?在Microsoft.AspNetCore.Mvc.MvcOptions中提供了 Conventions 属性:


  //
  // 摘要:
  //  Gets a list of Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention
  //  instances that will be applied to the Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel
  //  when discovering actions.
  public IList<IApplicationModelConvention> Conventions { get; }

通过操作它就能把自定义约定注入进去:


  services.AddMvc(options =>
   {
    options.Conventions.Add(new ApiControllerAuthorizeConvention());
   })

细心的人会发现,Conventions是一个 IApplicationModelConvention 类型的集合,而我们自定义的Convention是一个 IControllerModelConvention ,正常来说应该会报错才对?原因是Asp.Net Core的DI框架帮我们提供了一系列扩展方法来简化Convention的添加不用自己再去转换: