前言
我们在使用ASP.NET Core进行服务端应用开发的时候,或多或少都会涉及到使用Filter的场景。Filter简单来说是Action的拦截器,它可以在Action执行之前或者之后对请求信息进行处理。我们知道.Net Core默认是提供了IOC的功能,而且IOC是.Net Core的核心,.Net Core的底层基本上是基于IOC构建起来的,但是默认情况下自带的IOC不支持属性注入功能,但是我们在定义或使用Filter的时候有时候不得不针对某个Controller或Action,这种情况下我们不得不将Filter作为Attribute标记到Controller或Action上面,但是有时候Filter是需要通过构造函数注入依赖关系的,这个时候就有了一点小小的冲突,就是我们不得不解决在Controller或Action上使用Filter的时候,想办法去构建Filter的实例。本篇文章不是一篇讲解ASP.NET Core如何使用过滤器Filter的文章,而是探究一下Filter与IOC的奇妙关系的。
简单示例
咱们上面说过了,我们所用的过滤器即Filter,无论如何都是需要去解决与IOC的关系的,特别是在当Filter作用到某些具体的Controller或Action上的时候。因为直接标记的话必须要给构造函数传递初始化参数,但是这些参数是需要通过DI注入进去的,而不是手动传递。微软给我们提供了解决方案来解决这个问题,那就是使用
TypeFilterAttribute
或
ServiceFilterAttribute
,关于这两个Attribute使用的方式,咱们先通过简单的示例演示一下。首先定义一个Filter,模拟一下需要注入的场景
public class MySampleActionFilter : Attribute, IActionFilter{private readonly IPersonService _personService;private readonly ILogger<MySampleActionFilter> _logger;//模拟需要注入一些依赖关系public MySampleActionFilter(IPersonService personService, ILogger<MySampleActionFilter> logger){_personService = personService;_logger = logger;_logger.LogInformation($"MySampleActionFilter.Ctor {DateTime.Now:yyyyMMddHHmmssffff}");}public void OnActionExecuted(ActionExecutedContext context){Person personService = _personService.GetPerson(1);_logger.LogInformation($"TraceId=[{context.HttpContext.TraceIdentifier}] MySampleActionFilter.OnActionExecuted ");}public void OnActionExecuting(ActionExecutingContext context){_logger.LogInformation($"TraceId=[{context.HttpContext.TraceIdentifier}] MySampleActionFilter.OnActionExecuting ");}}
这里的日志功能ILogger在ASP.Net Core底层已经默认注入了,我们还模拟依赖了一些业务的场景,因此我们需要注入一些业务依赖,比如我们这里的PersonService。
public void ConfigureServices(IServiceCollection services){//模拟注册一下业务依赖services.AddScoped<IPersonService,PersonService>();services.AddControllers();}
单独使用Filter
这里我们先来演示一下单独在某些Controller或Action上使用Filter的情况,我们先来定义一个Action来模拟一下Filter的使用,由于Filter通过构造函数依赖了一下具体的服务所以我们先选择使用
TypeFilterAttribute
来演示,具体使用方式如下
[Route("api/[controller]/[action]")][ApiController]public class PersonController : ControllerBase{private readonly List<Person> _persons;public PersonController(){//模拟一下数据_persons = new List<Person>{new Person{ Id=1,Name="张三" },new Person{ Id=2,Name="李四" },new Person{ Id=3,Name="王五" }};}[HttpGet]//这里我们先通过TypeFilter的方式来使用定义的MySampleActionFilter[TypeFilter(typeof(MySampleActionFilter))]public List<Person> GetPersons(){return _persons;}}
然后我们运行起来示例,模拟请求一下GetPersons这个Action看一下效果,因为我们在定义的Filter中记录了日志信息,因此请求完成之后在控制台会打印出如下信息
info: Web5Test.MySampleActionFilter[0]MySampleActionFilter.Ctor 202110121820482450info: Web5Test.MySampleActionFilter[0]TraceId=[0HMCDD7ARPKDK:00000003] MySampleActionFilter.OnActionExecutinginfo: Web5Test.MySampleActionFilter[0]TraceId=[0HMCDD7ARPKDK:00000003] MySampleActionFilter.OnActionExecuted
这个时候我们将
TypeFilterAttribute
替换为
ServiceFilterAttribute
来看一下效果,替换后的Action是这个样子的
[HttpGet][ServiceFilter(typeof(MySampleActionFilter))]public List<Person> GetPersons(){return _persons;}
然后我们再来请求一下GetPersons这个Action,这个时候我们发现抛出了一个InvalidOperationException的异常,异常信息大致如下
System.InvalidOperationException: No service for type \'Web5Test.MySampleActionFilter\' has been registered.
从这个异常信息我们可以看出我们自定义的MySampleActionFilter过滤器需要注册到IOC中去,所以我们需要注册一下
public void ConfigureServices(IServiceCollection services){//模拟注册一下业务依赖services.AddScoped<IPersonService,PersonService>();//注册自定义的MySampleActionFilterservices.AddScoped<MySampleActionFilter>();services.AddControllers();}
做了如上的修改之后,我们再次启动项目请求一下GetPersons这个Action,这个时候MySampleActionFilter可以正常工作了。
这里简单的说明一下关于需要注册Filter的生命周期时,如果你不知道该注册成哪种生命周期的话那就注册成成
Scope
,这个是一种比较合理的方式,也就是和Controller生命周期保持一致每次请求创建一个实例即可。注册成单例的话很多时候会因为使用不当出现一些问题。
通过上面的演示我们大概了解了
TypeFilterAttribute
或
ServiceFilterAttribute
的使用方式和区别。
- 使用
TypeFilterAttribute
的时候我们的Filter过滤器是不需要注册到IOC中去的,因为它使用
Microsoft.Extensions.DependencyInjection.ObjectFactory
对Filte过滤器类型进行实例化
- 使用
ServiceFilterAttribute
的时候我们需要提前将我们定义的Filter注册到IOC容器中去,因为它使用容器来创建Filter的实例
全局注册的场景
很多时候呢,我们是针对全局使用Filter对所有的或者绝大多数的Action请求进行处理,这个时候我们会全局注册Filter而不需要在每个Controller或Action上一一注解。这个时候也涉及到关于Filter本身是否需要注册到IOC容器中的情况,这个地方需要注意的是Filter不是必须的需要托管到IOC容器当中去,但是一旦托管到IOC容器当中就需要注意不同注册Filter的方式,首先我们来看一下不将Filter注册到IOC的使用方式,还是那个示例
public void ConfigureServices(IServiceCollection services){services.AddScoped<IPersonService,PersonService>();services.AddControllers(options => {options.Filters.Add<MySampleActionFilter>();});}
只需要把自定义的MySampleActionFilter依赖的服务提前注册到IOC容器即可不需要多余的操作,这个时候MySampleActionFilter就可以正常的工作。还有一种方式就是你想让IOC容器去托管自定义的Filter,这个时候我们需要将Filter注册到容器中去,当然声明周期我们还是选择
Scope
,这个时候我们需要注意一下注册全局Filter的方式了,如下所示
public void ConfigureServices(IServiceCollection services){services.AddScoped<IPersonService,PersonService>();services.AddScoped<MySampleActionFilter>();services.AddControllers(options => {//这里需要注意注册Filter的方法应使用AddServiceoptions.Filters.AddService<MySampleActionFilter>();});}
如上面代码所示,为了能让Filter的实例来自于IOC容器,在注册全局Filter的时候我们应使用
AddService
方法完成注册,否则的话即使使用
Add
方法不会报错但是在IOC中你只能注册了个寂寞,总结一下全局注册的时候
- 如果你不想将全局注册的Filter托管到IOC容器中,那么需要使用
Add
方法,这样的话Filter实例则不会通过IOC容器创建
- 如果你想控制Filter实例的生命周期,则需要将Filter提前注册到IOC容器中去,这个时候注册全局Filter的时候就需要使用
AddService
方法,如果使用了
AddService
方法,但是你没有在IOC中注册Filter,则会抛出异常
源码探究
上面我们已经演示了将Filter托管到IOC容器和不使用IOC容器的使用方式,这方面微软考虑的也是很周到,不过就是容易让新手犯错。如果能熟练掌握,或者理解其中的工作原理的话,还是可以更好的使用这些,并且微软还为我们提供了一套灵活的扩展方式。想要更好的了解它们的工作方式,我们还得在源码下手。
TypeFilterAttribute
首先我们来看一下
TypeFilterAttribute
的源码,我们知道在某个Action上使用TypeFilterAttribute的时候是不要求将Filter注册到IOC中去的,因为这个时候Filter的实例是通过
ObjectFactory
创建出来的。在开始之前我们需要知道一个常识那就是在ASP.NET Core上我们所使用的Filter都必须要实现
IFilterMetadata
接口,这是ASP.NET Core底层知道Filter的唯一凭证,比如我们上面自定义的MySampleActionFilter是实现了IActionFilter接口,那么IActionFilter肯定是直接或间接的实现了IFilterMetadata接口,我们可以看一下IActionFilter接口的定义[点击查看源码?]
public interface IActionFilter : IFilterMetadata{void OnActionExecuting(ActionExecutingContext context);void OnActionExecuted(ActionExecutedContext context);}
通过上面的代码我们可以看到Filter本身肯定是要实现自IFilterMetadata接口的,这个是Filter的身份标识。接下来我们就来看一下TypeFilterAttribute源码的定义[点击查看源码?]
public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter{//创建Filter实例的工厂private ObjectFactory? _factory;public TypeFilterAttribute(Type type){ImplementationType = type ?? throw new ArgumentNullException(nameof(type));}/// <summary>/// 创建Filter时需要的构造参数/// </summary>public object[]? Arguments { get; set; }/// <summary>/// Filter实例的类型/// </summary>public Type ImplementationType { get; }/// <summary>/// Filter的优先级顺序/// </summary>public int Order { get; set; }/// <summary>/// 是否跨请求使用/// </summary>public bool IsReusable { get; set; }/// <summary>/// 创建Filter实例的实现方法/// </summary>public IFilterMetadata CreateInstance(IServiceProvider serviceProvider){if (serviceProvider == null){throw new ArgumentNullException(nameof(serviceProvider));}if (_factory == null){//获取自定义传递的初始化Filter实例的参数类型以创建ObjectFactoryvar argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();//通过ActivatorUtilities创建ObjectFactory_factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);}//通过IServiceProvider实例和传递的初始换参数得到IFilterMetadata实例即Filter实例var filter = (IFilterMetadata)_factory(serviceProvider, Arguments);//可以是嵌套的IFilterFactory实例if (filter is IFilterFactory filterFactory){filter = filterFactory.CreateInstance(serviceProvider);}//返回创建的IFilterMetadata实例return filter;}}
通过上面的代码我们可以得知
TypeFilterAttribute
中包含一个
CreateInstance
方法,而这个方法正是创建返回了一个
IFilterMetadata
实例即Filter实例,而创建IFilterMetadata实例则是通过
ActivatorUtilities
这个类创建的。在之前的文章中我们曾大致提到过这个类,ActivatorUtilities类可以借助IServiceProvider来创建一个具体的对象实例,所以当你不想使用DI的方式获取一个类的实例,但是这个类的依赖需要通过IOC容器去获得,那么可以借助ActivatorUtilities类来实现。需要注意的是
虽然Filter实例是通过ActivatorUtilities创建出来的,而且它的依赖项来自IOC容器,但是FIlter实例本身并不受IOC容器托管
。所以我们在使用的时候并没有将Filter注册到IOC容器中去。
ServiceFilterAttribute
上面我们看到了TypeFilterAttribute的实现方式,接下来我们来看一下和它类似的
ServiceFilterAttribute
的实现。我们知道ServiceFilterAttribute创建Filter实例必须要依赖IOC容器,即我们需要自行将Filter提前注册到IOC容器中去,这样才能通过ServiceFilterAttribute来正确的获取到Filter的实例,接下来我们就来通过源码来一探究竟[点击查看源码?]
public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter{/// <summary>/// 要实例化Filter的类型/// </summary>public ServiceFilterAttribute(Type type){ServiceType = type ?? throw new ArgumentNullException(nameof(type));}/// <summary>/// Filter执行的优先级顺序/// </summary>public int Order { get; set; }/// <summary>/// 要实例化Filter的类型/// </summary>public Type ServiceType { get; }/// <summary>/// 是否跨请求使用/// </summary>public bool IsReusable { get; set; }/// <summary>/// 创建Filter实例的实现方法/// </summary>public IFilterMetadata CreateInstance(IServiceProvider serviceProvider){if (serviceProvider == null){throw new ArgumentNullException(nameof(serviceProvider));}//直接在IServiceProvider实例中获取IFilterMetadata实例var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType);//支持IFilterFactory自身的嵌套执行if (filter is IFilterFactory filterFactory){filter = filterFactory.CreateInstance(serviceProvider);}return filter;}}
通过上面的代码我们可以看到ServiceFilterAttribute与TypeFilterAttribute的不同之处。首先ServiceFilterAttribute不支持手动传递初始化参数,因为它初始化的依赖全部来自于IOC容器。其次IFilterMetadata实例本身也是直接在IOC容器中获取的,而并不是仅仅只是依赖关系使用IOC容器。这也就是为何我们在使用ServiceFilterAttribute的时候需要自行先将Filter注册到IOC容器中去。
IFilterFactory
我们上面看到了无论是ServiceFilterAttribute还是TypeFilterAttribute,它们都是实现了
IFilterFactory
接口,它们之所以可以定义创建Filter实例的实现方法也完全是实现了
CreateInstance
方法,所以本质都是IFilterFactory。通过这个名字我们可以看出它是创建Filter的工厂,ServiceFilterAttribute和TypeFilterAttribute只是通过这个接口实现了自己创建IFilterFactory的逻辑。这是微软给我们提供的一个灵活之处,通过它我们可以在请求管道的任意位置创建Filter实例。接下来我们就来看一下IFilterFactory的定义[点击查看源码?]
public interface IFilterFactory : IFilterMetadata{/// <summary>/// 是否跨请求使用/// </summary>bool IsReusable { get; }/// <summary>/// 创建Filter实例/// </summary>/// <param name="serviceProvider">IServiceProvider实例</param>/// <returns>返回Filter实例</returns>IFilterMetadata CreateInstance(IServiceProvider serviceProvider);}
通过代码可知IFilterFactory也是实现了IFilterMetadata接口,所以它本身也是一个Filter,只是它比较特殊一些。既然它是一个Filter,但是它也很特殊,那么ASP.NET Core在使用的时候是如何区分是一个Filter实例,还是一个IFilterFactory实例呢?这两者存在一个本质的区别,Filter实例是可以直接在Action请求的时候拿来执行一些类似
OnActionExecuting
或
OnActionExecuted
的操作的,但是IFilterFactory实例需要先调用CreateInstance方法得到一个真正可以执行的Filter实例的。这个我们可以在
FilterProvider
中得到答案。
IFilterProvider
是用来定义提供Filter实现的操作,通过它我们可以得到可执行的Filter实例,在它的默认实现
DefaultFilterProvider
类中的
OnProvidersExecuting
方法里调用了它自身的
ProvideFilter
方法,看到方法的名字我们可以知道这是提供Filter实例之前的操作,在这里我们可以准备好Filter实例,我们来看一下OnProvidersExecuting方法的实现[点击查看源码?]
public void OnProvidersExecuting(FilterProviderContext context){//如果Action描述里的Filter描述存在,即存在Filter定义if (context.ActionContext.ActionDescriptor.FilterDescriptors != null){var results = context.Results;var resultsCount = results.Count;for (var i = 0; i < resultsCount; i++){//循环调用了ProvideFilter方法ProvideFilter(context, results[i]);}}}
这个方法通过判断执行的Action是否存在需要执行的Filter,如果存在则获取可执行的Filter实例,因为每个Action上可能存在许多个可执行的Filter,所以这里采用了循环操作,那么核心就在ProvideFilter方法[点击查看源码?]
public void ProvideFilter(FilterProviderContext context, FilterItem filterItem){if (filterItem.Filter != null){return;}var filter = filterItem.Descriptor.Filter;//如果Filter不是IFilterFactory实例则是可以直接使用的Filterif (filter is not IFilterFactory filterFactory){//直接赋值FilterfilterItem.Filter = filter;filterItem.IsReusable = true;}else{//如果是IFilterFactory实例//获取IOC容器实例即IServiceProvider实例var services = context.ActionContext.HttpContext.RequestServices;//调用IFilterFactory的CreateInstance得到Filter实例filterItem.Filter = filterFactory.CreateInstance(services);filterItem.IsReusable = filterFactory.IsReusable;if (filterItem.Filter == null){throw new InvalidOperationException();}ApplyFilterToContainer(filterItem.Filter, filterFactory);}}
通过这个代码我们就可以看出,这里会判断Filter是常规的IFilterMetadata实例还是IFilterFactory实例,如果是IFilterFactory则需要调用它的CreateInstance方法得到一个可以直接使用的Filter实例,否则就可以直接使用这个Filter了。所以我们注册Filter的时候可以是任何IFilterMetadata实例,但是真正执行的时候需要转换成统一的可直接执行的类似ActionFilter的实例。既然ServiceFilterAttribute和TypeFilterAttribute可以实现自IFilterFactory接口,那么我们完全可以自己通过IFilterFactory接口来实现一个Filter创建的工厂,这样的话为我们创建Filter提供了另一种思路,我们以我们上面自定义的MySampleActionFilter为例,为它创建一个MySampleActionFilterFactory工厂,实现代码如下
public class MySampleActionFilterFactory : Attribute, IFilterFactory{public bool IsReusable => false;public IFilterMetadata CreateInstance(IServiceProvider serviceProvider){//我们这里模拟通过IServiceProvider获取依赖的实例IPersonService personService = serviceProvider.GetService<IPersonService>();ILogger<MySampleActionFilter> logger = serviceProvider.GetService<ILogger<MySampleActionFilter>>();//通过依赖构造MySampleActionFilter实例并返回return new MySampleActionFilter(personService,logger);}}
这样的话我们可以把MySampleActionFilterFactory同样作用于上面的示例代码中去,如下所示,执行效果是一样的
[HttpGet]//[ServiceFilter(typeof(MySampleActionFilter))][MySampleActionFilterFactory]public List<Person> GetPersons(){return _persons;}
全局注册
之前我们通过示例看到,全局注册Filter的时候也存在是否将Filter注册到IOC容器的这种情况。既可以注册到IOC容器,也可以不注册到IOC容器,只不过添加过滤器的方法不一样,看着也挺神奇的,但是一旦用错IOC就容易注册了个寂寞。我们知道全局注册Filter的时候承载Filter的本质是一个集合,这个集合的名字叫
FilterCollection
,这里我们只关注它的Add方法和AddService方法即可。FilterCollection继承自
Collection<IFilterMetadata>
。在.Net Core中微软的代码风格是用特定的类继承自已有的泛型操作,这样的话可以让开发者更关注类功能的本身,而且还可以防止书写泛型出错,是个不错的思路。Add存在好几个重载方法但是本质都是调用最全的哪一个方法,接下来我们就来先看一下最本质的Add方法[点击查看源码?]
public IFilterMetadata Add(Type filterType, int order){if (filterType == null){throw new ArgumentNullException(nameof(filterType));}//不是IFilterMetadata类型添加会报错if (!typeof(IFilterMetadata).IsAssignableFrom(filterType)){throw new ArgumentException();}//最终还是将注册的Filter类型包装成TypeFilterAttributevar filter = new TypeFilterAttribute(filterType) { Order = order };Add(filter);return filter;}
有点意思,豁然开朗了,通过Add方法全局添加的Filter本质还是包装成了TypeFilterAttribute,这也就解释了为啥我们可以不用再IOC容器中注册Filter而之前使用Filter了原因就是TypeFilterAttribute帮我们创建了。那接下来我们再来看看AddService方法的实现[点击查看源码?]
public IFilterMetadata AddService(Type filterType, int order){if (filterType == null){throw new ArgumentNullException(nameof(filterType));}//不是IFilterMetadata类型添加会报错if (!typeof(IFilterMetadata).IsAssignableFrom(filterType)){throw new ArgumentException();}//最终还是将注册的Filter类型包装成ServiceFilterAttributevar filter = new ServiceFilterAttribute(filterType) { Order = order };Add(filter);return filter;}
同理AddService本质是将注册的Filter类型包装成了ServiceFilterAttribute,所以我们如果已经提前在IOC中注册了Filter,那么我们只需要直接使用AddService注册Filter即可。当然如果你不知道这个方法而是使用了Add方法也不会报错,只是IOC容器可能有点寂寞。不过微软的这思路确实值得我们学习,这种情况下处理逻辑是统一的,最终都是来自
IFilterFactory
这个接口。
总结
通过本篇文章我们了解了在ASP.NET Core使用Filter的时候,Filter有构建实例的方式,即可以将Filter注册到IOC容器中去,也可以不用注册。区别就是你是否可以自行控制Filter实例的生命周期,整体来说微软的设计思路还是非常合理的,有助于我们统一处理Filter实例的生成。我们都知道自带的IOC只支持构造注入这样的话就给特定的Action构建Filter的时候带来了不便,微软给出了
TypeFilterAttribute
和
ServiceFilterAttribute
解决方案,接下来我们就总结一下它们俩
- TypeFilterAttribute和ServiceFilterAttribute都实现了
IFilterFactory
接口,只是创建Filter实例的方式不同。
- TypeFilterAttribute通过
ActivatorUtilities
创建Filter实例,虽然它的依赖模块来自IOC容器,但是Filter实例本身并不受IOC容器管理。
- ServiceFilterAttribute则是通过
IServiceProvider
获取了Filter实例,这样整个Filter是受到IOC容器管理的,注入当然是基础操作了。
- 全局注册Filter的时候如果没有将Filter注册到IOC容器中,则使用
Add方法
添加过滤器,Add方法的本质是将注册的Filter包装成TypeFilterAttribute
- 如果全局注册Filter的时候Filter已经提前注册到IOC容器中,则使用
AddService方法
添加过滤器,AddService方法的本质是将注册的Filter包装成ServiceFilterAttribute
通过上面的描述相信大家能更好的理解Filter本身与IOC容器的关系,这样的话也能帮助大家在具体使用的时候知道如何去用,如何更合理的使用。这里我们是用的IActionFilter作为示例,不过没有没关系,只要是实现了IFilterMetadata接口的都是一样的,即所有的操作都是针对接口的,这也是面向对象编程的本质。如果有更多疑问,或作者描述不正确,欢迎大家评论区讨论。
?欢迎扫码关注我的公众号?