• asp.net core之依赖注入


    依赖注入概念#

    ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。
    按照官方文档的描述:
    依赖关系注入通过以下方式解决了这些问题:

    • 使用接口或基类将依赖关系实现抽象化。
    • 在服务容器中注册依赖关系。 ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。 服务通常已在应用的 Program.cs 文件中注册。
    • 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。

    探索Asp.net core中的依赖注入#

    生命周期#

    在asp.net core中,以来注入有三个生命周期。
    分别为Singleton(单例),Scoped(范围),Transient(瞬态)。
    Singleton(单例),很好理解,就是一个单例模式,在整个应用的生命周期中只会初始化一次。
    Scoped(范围),每一次请求中实例化一次。
    Transient(瞬态),每次使用都是一个新的实例化对象。
    注入方式分别如下:

    services.AddSingleton(); //单例
    services.AddScoped(); //范围
    services.AddTransient(); //瞬态
    

    来实践一下,用VS新建一个WebApi项目,然后添加三个类,对应三个生命周期。

    public class TestTransient
    {
        public TestTransient()
        {
            Id = Guid.NewGuid();
        }
    
        public Guid Id { get; set; }
    }
    
    public class TestSingleton
    {
        public TestSingleton()
        {
            Id = Guid.NewGuid();
        }
    
        public Guid Id { get; set; }
    }
    
    public class TestScoped
    {
        public TestScoped()
        {
            Id = Guid.NewGuid();
        }
    
        public Guid Id { get; set; }
    }
    

    然后在Program中添加注入,这里我没用接口注入,直接注入类,我们也可以使用接口注入的方式。

    builder.Services.AddSingleton();
    builder.Services.AddScoped();
    builder.Services.AddTransient();
    

    接下来我们在控制器中通过构造函数注入我们的三个类。

    private readonly ILogger _logger;
    private readonly TestScoped _testScoped;
    private readonly TestSingleton _testSingleton;
    private readonly TestTransient _testTransient;
    
    public WeatherForecastController(ILogger logger, TestScoped testScoped, TestSingleton testSingleton, TestTransient testTransient)
    {
        _logger = logger;
        _testScoped = testScoped;
        _testSingleton = testSingleton;
        _testTransient = testTransient;
    }
    

    在调用Get方法中打印我们的Id
    第一次请求
    image.png
    第二第三次请求
    image.png
    可以看到单例的Id每次请求都是一致的,而范围和瞬态的在不同请求中都不一样。
    那么如何区别Scoped和Transient呢?很简单,我们直接整一个简单的中间件,分别注入并答应对应Id。

    app.Use(async (httpContext, next) => 
    {
        var scoped = httpContext.RequestServices.GetRequiredService();
        var transient = httpContext.RequestServices.GetRequiredService();
        Console.WriteLine($"Middleware scoped: {scoped.Id}");
        Console.WriteLine($"Middleware transient: {transient.Id}");
        await next(httpContext);
    });
    

    image.png
    可以看到,在一次请求中Scoped的Id是一致的,Transient的Id每次都不一样。

    服务注册方法#

    在上面中我只是用了其中一种注册方法,就是直接注册类。
    除此之外,我们还可以通过接口注入。
    比如我们添加一个IScopedDependency的接口,然后新建一个TestAbcScoped继承IScopedDependency,然后在Program中添加注入

    builder.Services.AddScoped();
    

    之后我们在构造器中使用IScopedDependency注入的话,则自动会获得TestAbcScoped的实现实例。
    image.png
    通过我们Debug监视,可以发现IScopedDependency注入的实例确实是TestAbcScoped。
    当我们注册同一个接口的多个实现时,默认取最后一次注入的实例,当我们需要获取全部接口的实现时,可以通过注入IEnumerable获取该接口的所有实现。
    我们增加一个IScopedDependency的实现

    public class TestAbcScoped : IScopedDependency
    {
    }
    public class TestAbcdScoped : IScopedDependency
    {
    }
    

    注册顺序为:

    builder.Services.AddScoped();
    builder.Services.AddScoped();
    

    image.png
    可以看到,单个注入会取后注入的实例,IEnumerable注入则会获取所有的实例。
    注意:
    除此之外,还有TryAddXXX的方法,注册服务时,如果还没有添加相同类型的实例,就添加一个实例。
    服务注册通常与顺序无关,除了注册同一类型的多个实现时。

    服务注入#

    上面我们实操时所用的注入方法都是构造器注入,这也是官方推荐的注入方式。
    除此之外,我们还可以使用IServiceProvider获取服务,上面中间件所用到的HttpContext.RequestService本质是一个IServiceProvider实例。
    三方框架加持注入功能,asp.net core的注入方式有限,我们可以使用Autofac来增强。
    使用autofac之后我们可以支持属性注入,即无需在构造器中添加,只需要构造对应的属性即可。
    属性注入和构造器注入的优缺点对比。
    构造器注入可以清晰的看出我们所有注入的实例,对于协作和沟通有比较大的帮助。但是,若是注入的东西太多,会导致一个很庞大的构造器,当然官方的建议是,当存在那么多的注入的时候,就需要考虑拆分业务了。
    属性注入则只需要通过构造一个属性,系统自动注入,弱点是没有构造器清晰辨别。毕竟不容易区分哪些属性是通过注入的,哪些是业务赋值的。
    在考虑到继承方面时,有时候属性注入会比构造器注入合适,比如在基类中,我们往往可以注入通用的服务,这样在子类的构造器中就无需再次注入该服务。

    注意事项#

    在使用依赖注入的时候,我们最好要明确每个服务的生命周期,在长生命周期的服务中,切勿注入短生命周期的服务。
    如在单例中注入范围服务或瞬时服务,在范围服务中注入瞬时服务。否则会出现对象已被释放的情况。
    在新版本中,单例里面注入范围服务,程序会自动检测并提示异常。但是在旧版本中是没有提示的,这点需要注意。
    image.png

    如何在单例中使用Scoped范围服务呢,可以使用IServiceScopeFactory,IServiceScopeFactory始终注册为单例实例,通过IServiceScopeFactory创建一个Scope生命周期。

    public class TestSingleton
    {
        private readonly IServiceScopeFactory _serviceScopeFactory;
        public TestSingleton(IServiceScopeFactory serviceScopeFactory)
        {
            _serviceScopeFactory = serviceScopeFactory;
            Id = Guid.NewGuid();
        }
    
        public Guid Id { get; set; }
    
        public void Console()
        {
            using(var scope = _serviceScopeFactory.CreateScope()) 
            {
                var testScoped = scope.ServiceProvider.GetRequiredService();
                System.Console.WriteLine($"TestSingleton - TestScoped: {testScoped.Id}");
            }
        }
    }
    

    再次启动服务正常,并且请求可以看到,我们CreateScope后,生成的Id也是跟请求中的Scoped不一样的,因为他们属于不同的Scoped。
    image.png

    欢迎进群催更。

  • 相关阅读:
    VScode配置java环境 连接mysql
    【Node.js】 第四章 模块化
    LeetCode 0257. 二叉树的所有路径
    【Linux】vim 使用
    2022PMP项目管理认证考试报考指南(2)
    dcase_util教程
    分割学习(loss and Evaluation)
    MySQL的卸载与安装(Linux)
    抽奖动画 - 红包雨抽奖
    [Pandas技巧] 分组比例计算求和
  • 原文地址:https://www.cnblogs.com/fanshaoO/p/17579976.html