从何说起

这来自于我把项目迁移到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

在Asp.Net Core中使用ModelConvention实现全局过滤器隔离的更多相关文章

  1. 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

    一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...

  2. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  3. Asp.net Core中使用Session

    前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...

  4. 在ASP.NET Core中使用百度在线编辑器UEditor

    在ASP.NET Core中使用百度在线编辑器UEditor 0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个.不过服务端只有ASP.NET版的,如果是为了 ...

  5. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  6. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  7. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

  8. ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理

    ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...

  9. ASP.NET Core 中文文档 第二章 指南(4.6)Controller 方法与视图

    原文:Controller methods and views 作者:Rick Anderson 翻译:谢炀(Kiler) 校对:孟帅洋(书缘) .张仁建(第二年.夏) .许登洋(Seay) .姚阿勇 ...

随机推荐

  1. MaxCompute Studio使用心得系列7——作业对比

    在数据开发过程中,我们通常需要将两个作业进行对比从而定位作业运行性能或者结果有差异的问题,但是对比作业时需要同时打开两个studio 的tab页,或者两个Logview页,不停切换进行对比,使用起来非 ...

  2. Pytorch使用tensorboardX网络结构可视化。超详细!!!

    https://www.jianshu.com/p/46eb3004beca 1 引言 我们都知道tensorflow框架可以使用tensorboard这一高级的可视化的工具,为了使用tensorbo ...

  3. dataframe构建

    data=[[[0],1]]df = pd.DataFrame(data, columns=['col1', 'col2']) df = pd.DataFrame({‘col1’:‘’,‘col2’: ...

  4. Oracle基础学习4--Oracle权限传递

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/wang379275614/article/details/32215325 以下将用一个实例来解说: ...

  5. Hadoop应用程序示例

  6. @游记@ CQOI2019(十二省联考)

    目录 @day - 0@ @day - 1@ @day - 2@ @后记@ 我只是来打酱油哒-- 顶多能进个 E 类继续打酱油. 原本还在互奶 A 队,结果现在--铁定进不了队啦. 对初中生的歧视啊 ...

  7. 微信授权登录-微信公众号和PC端网站

    https://blog.csdn.net/qq_34664239/article/details/79107529 一.微信公众号授权登录——微信公众平台 微信授权登录,并调用后台接口,获取用户信息 ...

  8. 禁用gpu首选

    import osos.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"os.environ["CUDA_VIS ...

  9. CSS定位方式有哪些?position属性的值有哪些?他们之间的区别是什么?

    在CSS中关于定位的内容是:position:relative | absolute | static | fixed • static 自动定位,自动定位就是元素在页 面普通文档流中由HTML自动定 ...

  10. SpringBoot2.0--- 多数据源配置

      在开发的过程中我们可能都会遇到对接公司其他系统等需求,对于外部的系统可以采用接口对接的方式,对于一个公司开发的两个系统,并且知道相关数据库结构的情况下,就可以考虑使用多数据源来解决这个问题.Spr ...