青海省建设厅网站公示公告,网站上面带官网字样怎么做的,专业零基础网站建设教学服务,定制软件开发文案前言我们在使用ASP.NET Core进行服务端应用开发的时候#xff0c;或多或少都会涉及到使用Filter的场景。Filter简单来说是Action的拦截器#xff0c;它可以在Action执行之前或者之后对请求信息进行处理。我们知道.Net Core默认是提供了IOC的功能#xff0c;而且IOC是.Net Co… 前言 我们在使用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 ILoggerMySampleActionFilter _logger;//模拟需要注入一些依赖关系public MySampleActionFilter(IPersonService personService, ILoggerMySampleActionFilter 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.AddScopedIPersonService,PersonService();services.AddControllers();
}单独使用Filter这里我们先来演示一下单独在某些Controller或Action上使用Filter的情况我们先来定义一个Action来模拟一下Filter的使用由于Filter通过构造函数依赖了一下具体的服务所以我们先选择使用TypeFilterAttribute来演示具体使用方式如下[Route(api/[controller]/[action])]
[ApiController]
public class PersonController : ControllerBase
{private readonly ListPerson _persons;public PersonController(){//模拟一下数据_persons new ListPerson{new Person{ Id1,Name张三 },new Person{ Id2,Name李四 },new Person{ Id3,Name王五 }};}[HttpGet]//这里我们先通过TypeFilter的方式来使用定义的MySampleActionFilter[TypeFilter(typeof(MySampleActionFilter))]public ListPerson GetPersons(){return _persons;}
}然后我们运行起来示例模拟请求一下GetPersons这个Action看一下效果因为我们在定义的Filter中记录了日志信息因此请求完成之后在控制台会打印出如下信息info: Web5Test.MySampleActionFilter[0]MySampleActionFilter.Ctor 202110121820482450
info: Web5Test.MySampleActionFilter[0]TraceId[0HMCDD7ARPKDK:00000003] MySampleActionFilter.OnActionExecuting
info: Web5Test.MySampleActionFilter[0]TraceId[0HMCDD7ARPKDK:00000003] MySampleActionFilter.OnActionExecuted这个时候我们将TypeFilterAttribute替换为ServiceFilterAttribute来看一下效果替换后的Action是这个样子的[HttpGet]
[ServiceFilter(typeof(MySampleActionFilter))]
public ListPerson 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.AddScopedIPersonService,PersonService();//注册自定义的MySampleActionFilterservices.AddScopedMySampleActionFilter();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.AddScopedIPersonService,PersonService();services.AddControllers(options {options.Filters.AddMySampleActionFilter();});
}只需要把自定义的MySampleActionFilter依赖的服务提前注册到IOC容器即可不需要多余的操作这个时候MySampleActionFilter就可以正常的工作。还有一种方式就是你想让IOC容器去托管自定义的Filter这个时候我们需要将Filter注册到容器中去当然声明周期我们还是选择Scope这个时候我们需要注意一下注册全局Filter的方式了如下所示public void ConfigureServices(IServiceCollection services)
{services.AddScopedIPersonService,PersonService();services.AddScopedMySampleActionFilter();services.AddControllers(options {//这里需要注意注册Filter的方法应使用AddServiceoptions.Filters.AddServiceMySampleActionFilter();});
}如上面代码所示为了能让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接口的定义[点击查看源码[1]]public interface IActionFilter : IFilterMetadata
{void OnActionExecuting(ActionExecutingContext context);void OnActionExecuted(ActionExecutedContext context);
}通过上面的代码我们可以看到Filter本身肯定是要实现自IFilterMetadata接口的这个是Filter的身份标识。接下来我们就来看一下TypeFilterAttribute源码的定义[点击查看源码[2]]public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{//创建Filter实例的工厂private ObjectFactory? _factory;public TypeFilterAttribute(Type type){ImplementationType type ?? throw new ArgumentNullException(nameof(type));}/// summary/// 创建Filter时需要的构造参数/// /summarypublic object[]? Arguments { get; set; }/// summary/// Filter实例的类型/// /summarypublic Type ImplementationType { get; }/// summary/// Filter的优先级顺序/// /summarypublic int Order { get; set; }/// summary/// 是否跨请求使用/// /summarypublic bool IsReusable { get; set; }/// summary/// 创建Filter实例的实现方法/// /summarypublic 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的实例接下来我们就来通过源码来一探究竟[点击查看源码[3]]public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{/// summary/// 要实例化Filter的类型/// /summarypublic ServiceFilterAttribute(Type type){ServiceType type ?? throw new ArgumentNullException(nameof(type));}/// summary/// Filter执行的优先级顺序/// /summarypublic int Order { get; set; }/// summary/// 要实例化Filter的类型/// /summarypublic Type ServiceType { get; }/// summary/// 是否跨请求使用/// /summarypublic bool IsReusable { get; set; }/// summary/// 创建Filter实例的实现方法/// /summarypublic 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的定义[点击查看源码[4]]public interface IFilterFactory : IFilterMetadata
{/// summary/// 是否跨请求使用/// /summarybool IsReusable { get; }/// summary/// 创建Filter实例/// /summary/// param nameserviceProviderIServiceProvider实例/param/// returns返回Filter实例/returnsIFilterMetadata 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方法的实现[点击查看源码[5]]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方法[点击查看源码[6]]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.GetServiceIPersonService();ILoggerMySampleActionFilter logger serviceProvider.GetServiceILoggerMySampleActionFilter();//通过依赖构造MySampleActionFilter实例并返回return new MySampleActionFilter(personService,logger);}
}这样的话我们可以把MySampleActionFilterFactory同样作用于上面的示例代码中去如下所示执行效果是一样的[HttpGet]
//[ServiceFilter(typeof(MySampleActionFilter))]
[MySampleActionFilterFactory]
public ListPerson GetPersons()
{return _persons;
}全局注册之前我们通过示例看到全局注册Filter的时候也存在是否将Filter注册到IOC容器的这种情况。既可以注册到IOC容器也可以不注册到IOC容器只不过添加过滤器的方法不一样看着也挺神奇的但是一旦用错IOC就容易注册了个寂寞。我们知道全局注册Filter的时候承载Filter的本质是一个集合这个集合的名字叫FilterCollection,这里我们只关注它的Add方法和AddService方法即可。FilterCollection继承自CollectionIFilterMetadata。在.Net Core中微软的代码风格是用特定的类继承自已有的泛型操作这样的话可以让开发者更关注类功能的本身而且还可以防止书写泛型出错是个不错的思路。Add存在好几个重载方法但是本质都是调用最全的哪一个方法接下来我们就来先看一下最本质的Add方法[点击查看源码[7]]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方法的实现[点击查看源码[8]]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接口的都是一样的即所有的操作都是针对接口的这也是面向对象编程的本质。如果有更多疑问或作者描述不正确欢迎大家评论区讨论。References[1] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Abstractions/src/Filters/IActionFilter.cs[2] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/TypeFilterAttribute.cs[3] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/ServiceFilterAttribute.cs[4] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Abstractions/src/Filters/IFilterFactory.cs[5] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/Filters/DefaultFilterProvider.cs#L15[6] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/Filters/DefaultFilterProvider.cs#L39[7] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/Filters/FilterCollection.cs#L79[8] 点击查看源码: https://hub.fastgit.org/dotnet/aspnetcore/blob/v6.0.0-rc.2.21480.10/src/Mvc/Mvc.Core/src/Filters/FilterCollection.cs#L163