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

 更新时间:2021年9月22日 10:00  点击:1650

从何说起

这来自于我把项目迁移到Asp.Net Core的过程中碰到一个问题。在一个web程序中同时包含了MVC和WebAPI,现在需要给WebAPI部分单独添加一个接口验证过滤器 IActionFilter ,常规做法一般是写好过滤器后给需要的控制器挂上这个标签,高级点的做法是注册一个全局过滤器,这样可以避免每次手动添加同时代码也更好管理。注册全局过滤器的方式为:

services.AddMvc(options =>
 {
  options.Filters.Add(typeof(AccessControlFilter));
 });

但这样做会带来一个问题,那就是MVC部分控制器也会受影响,虽然可以在过滤器中进行一些判断来区分哪些是MVC Controller哪些是API Controller,但是平白无故给MVC增加这么一个没用的Filter,反正我是不能忍,所以寻找有没有更好的办法来实现这个功能。

于是ModelConvention(可以翻译为模型约定)闪亮登场。

先认识下ApplicationModel

看一下官方文档是怎么描述应用程序模型(ApplicationModel)的:

ASP.NET Core MVC defines an application model representing the components of an MVC app. You can read and manipulate this model to modify how MVC elements behave. By default, MVC follows certain conventions to determine which classes are considered to be controllers, which methods on those classes are actions, and how parameters and routing behave. You can customize this behavior to suit your app's needs by creating your own conventions and applying them globally or as attributes.

简单一点说,ApplicationModel描述了MVC应用中的各种对象和行为,这些内容包含Application、Controller、Action、Parameter、Router、Page、Property、Filter等等,而Asp.Net Core框架本身内置一套规则(Convention)用来处理这些模型,同时也提供了接口给我们自定义约定来扩展模型以实现更符合需要的应用。

和应用程序模型有关的类都定义在命名空间 Microsoft.AspNetCore.Mvc.ApplicationModels 中,这些模型通过 IApplicationModelProvider 构建出来,Asp.Net Core框架提供的默认Provider是 DefaultApplicationModelProvider 。我们可以编辑这些模型,从而更改它的表现行为,这就要借助它的ModelConvention来实现。

ModelConvention

ModelConvention定义了操作模型的入口,又或者说是一种契约,通过它我们可以对模型进行修改,常用的Convention包括:

  • IApplicationModelConvention
  • IControllerModelConvention
  • IActionModelConvention
  • IParameterModelConvention
  • IPageRouteModelConvention

这些接口提供了一个共同的方法 Apply ,方法参数是各自的应用程序模型,以 IControllerModelConvention 为例看一下它的定义:

namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
 //
 // 摘要:
 //  Allows customization of the Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel.
 //
 // 言论:
 //  To use this interface, create an System.Attribute class which implements the
 //  interface and place it on a controller class. Microsoft.AspNetCore.Mvc.ApplicationModels.IControllerModelConvention
 //  customizations run after Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention
 //  customizations and before Microsoft.AspNetCore.Mvc.ApplicationModels.IActionModelConvention
 //  customizations.
 public interface IControllerModelConvention
 {
  //
  // 摘要:
  //  Called to apply the convention to the Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel.
  //
  // 参数:
  // controller:
  //  The Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel.
  void Apply(ControllerModel controller);
 }
}

从接口摘要可以看到,这个接口允许自定义 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的添加不用自己再去转换:

通过代码调试发现,应用启动时遍历了系统中的所有控制器去执行Apply操作,那么通过 IApplicationModelConvention 一样也能实现这个功能,因为它里面包含了控制器集合:

 public class ApiControllerAuthorizeConvention : IApplicationModelConvention
 {
  public void Apply(ApplicationModel application)
  {
   foreach (var controller in application.Controllers)
   {
    if (controller.Filters.Any(x => x is ApiControllerAttribute) && !controller.Filters.Any(x => x is AccessControlFilter))
    {
     controller.Filters.Add(new AccessControlFilter());
    }
   }
  }
 }

再改进一下

实际开发中我的AccessControlFilter需要通过构造函数注入业务接口,类似于这样:

 public class AccessControlFilter : IActionFilter
 {
  private IUserService _userService;

  public AccessControlFilter(IUserService service)
  {
   _userService = service;
  }

  public void OnActionExecuting(ActionExecutingContext context)
  {
    //模拟一下业务操作
    //var user=_userService.GetById(996);
    //.......
  }

  public void OnActionExecuted(ActionExecutedContext context)
  {
  }
 }

如何优雅的在Convention中使用DI自动注入呢?Asp.Net Core MVC框架提供的 ServiceFilter 可以解决这个问题, ServiceFilter 本身是一个过滤器,它的不同之处在于能够通过构造函数接收一个Type类型的参数,我们可以在这里把真正要用的过滤器传进去,于是上面的过滤器注册过程演变为:

 controller.Filters.Add(new ServiceFilterAttribute(typeof(AccessControlFilter)));

当然了,要从DI中获取这个filter实例,必须要把它注入到DI容器中:

 services.AddScoped<AccessControlFilter>();

至此,大功告成,继续愉快的CRUD。

突然想起来我上篇文章提到的扩展DI属性注入功能估计也能通过这个玩意实现,eeeeeee...有空了试一下。

总结

总体来说,我通过曲线救国的方式实现了全局过滤器隔离,虽然去遍历目标控制器再手动添加Filter的方式没有那种一行代码就能实现的方式优雅,但我大体来说还算满意,是目前能想到的最好办法。我估摸着, options.Filters.Add(xxx) 也是在框架某个时候一个个把xxx丢给各自主人的,瞎猜的,说错不负责~hhhh:see_no_evil::see_no_evil::see_no_evil:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • ASP.NET购物车实现过程详解

    这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • .NET Core下使用Kafka的方法步骤

    这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 在ASP.NET 2.0中操作数据之七十二:调试存储过程

    在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
  • 详解.NET Core 3.0 里新的JSON API

    这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • ASP.NET Core根据环境变量支持多个 appsettings.json配置文件

    这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 记一次EFCore类型转换错误及解决方案

    这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
  • 详解ASP.NET Core 中基于工厂的中间件激活的实现方法

    这篇文章主要介绍了ASP.NET Core 中基于工厂的中间件激活的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • Vue封装全局过滤器Filters的步骤

    这篇文章主要介绍了Vue封装全局过滤器Filters的步骤,通过封装vue全局过滤器实现filters的统一管理。感兴趣的朋友可以了解下...2020-09-16
  • asp.net通过消息队列处理高并发请求(以抢小米手机为例)

    这篇文章主要介绍了asp.net通过消息队列处理高并发请求(以抢小米手机为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • ASP.NET单选按钮控件RadioButton常用属性和方法介绍

    RadioButton又称单选按钮,其在工具箱中的图标为 ,单选按钮通常成组出现,用于提供两个或多个互斥选项,即在一组单选钮中只能选择一个...2021-09-22
  • ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表

    在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有...2016-05-19
  • Vue的全局过滤器和私有过滤器的实现

    这篇文章主要介绍了Vue的全局过滤器和私有过滤器的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-20
  • 详解.NET Core 使用HttpClient SSL请求出错的解决办法

    这篇文章主要介绍了.NET Core 使用HttpClient SSL请求出错的解决办法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-09-22
  • ASP.NET中iframe框架点击左边页面链接 右边显示链接页面内容

    这篇文章主要介绍了ASP.NET中iframe框架点击左边页面链接,右边显示链接页面内容的实现代码,感兴趣的小伙伴们可以参考一下...2021-09-22
  • 创建一个完整的ASP.NET Web API项目

    ASP.NET Web API具有与ASP.NET MVC类似的编程方式,ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。下面创建一个简单的Web API项目,需要的朋友可以参考下...2021-09-22
  • ASP.NET连接MySql数据库的2个方法及示例

    这篇文章主要介绍了ASP.NET连接MySql数据库的2个方法及示例,使用的是MySQL官方组件和ODBC.NET,需要的朋友可以参考下...2021-09-22
  • Asp.Net使用Bulk实现批量插入数据

    这篇文章主要介绍了Asp.Net使用Bulk实现批量插入数据的方法,对于进行asp.net数据库程序设计非常有借鉴价值,需要的朋友可以参考下...2021-09-22
  • Asp.net中获取DataTable选择第一行某一列值

    这篇文章主要介绍了获取DataTable选择第一行某一列值,需要的朋友可以参考下...2021-09-22
  • Asp.net动态生成html页面的方法分享

    这篇文章介绍了Asp.net动态生成html页面的方法,有需要的朋友可以参考一下...2021-09-22