ASP.NET Core Filter与IOC的羁绊

2021/10/14 9:44:09

本文主要是介绍ASP.NET Core Filter与IOC的羁绊,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

    我们在使用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注入进去的,而不是手动传递。微软给我们提供了解决方案来解决这个问题,那就是使用TypeFilterAttributeServiceFilterAttribute,关于这两个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 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 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>();
    //注册自定义的MySampleActionFilter
    services.AddScoped<MySampleActionFilter>();
    services.AddControllers();
}

做了如上的修改之后,我们再次启动项目请求一下GetPersons这个Action,这个时候MySampleActionFilter可以正常工作了。

这里简单的说明一下关于需要注册Filter的生命周期时,如果你不知道该注册成哪种生命周期的话那就注册成成Scope,这个是一种比较合理的方式,也就是和Controller生命周期保持一致每次请求创建一个实例即可。注册成单例的话很多时候会因为使用不当出现一些问题。

通过上面的演示我们大概了解了TypeFilterAttributeServiceFilterAttribute的使用方式和区别。

  • 使用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的方法应使用AddService
        options.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接口的定义[点击查看源码



这篇关于ASP.NET Core Filter与IOC的羁绊的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程