写点什么

关于 ASP.NET Core 内置的依赖注入

作者:雄鹿 @
  • 2024-03-25
    广东
  • 本文字数:3450 字

    阅读完需:约 11 分钟

关于 ASP.NET Core 内置的依赖注入

本文章将介绍 ASP.NET Core 内置的依赖注入及其生命周期和策略,并使用 Code 帮助理解

先决条件

.NET 8 SDK

介绍

ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式

这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术

使用容器注入和创建对象

首先准备三个类供后面的容器创建对象

    public interface ILogger { }    public class Logger : ILogger { }
public interface IServer { } public class Server : IServer { }
public interface IHttp { } public class Http : IHttp { }
复制代码


将三个类注册到不同的生命周期中,然后循环打印注册的类型信息

最后 Build 出 容器(服务提供对象),使用 容器 创建对象,并断言对象是否为预期的对象

    var collection = new ServiceCollection()        .AddTransient<ILogger, Logger>()        .AddScoped<IServer, Server>()        .AddSingleton<IHttp, Http>();        foreach (var item in collection)    {    	var log = $"Service: {item.ServiceType.FullName} Lifetime: {item.Lifetime}\nInstance: {item.ImplementationType?.FullName}\n";        Console.WriteLine(log);    }        var provider = collection.BuildServiceProvider();    Debug.Assert(provider.GetService<ILogger>() is Logger);    Debug.Assert(provider.GetService<IServer>() is Server);    Debug.Assert(provider.GetService<IHttp>() is Http);
复制代码

基类或接口被多次注册

首先准备三个类并继承同一个基类

    public class Base { }
public interface ILogger { } public class Logger : Base, ILogger { }
public interface IServer { } public class Server : Base, IServer { }
public interface IHttp { } public class Http : Base, IHttp { }
复制代码


将三个类注册到容器之后,Build 出 容器(服务提供对象)

使用 容器 创建对象,并断言重复注册的基类得到的对象是最后一个

使用 容器 的 GetServices 方法可以获取所有注册类型的对象,并按照注册顺序排列

    var collection = new ServiceCollection()        .AddTransient<Base, Logger>()        .AddScoped<Base, Server>()        .AddScoped<Base, Http>();        var provider = collection.BuildServiceProvider();        Debug.Assert(provider.GetService<Base>() is Http);        var bases = provider.GetServices<Base>().ToList();    Debug.Assert(bases[0] is Logger);    Debug.Assert(bases[1] is Server);    Debug.Assert(bases[2] is Http);
复制代码

根容器和子容器创建实例

ASP.NET Core 内置的依赖注入框架 有个根容器和子容器的概念

  • 子容器只关心根容器,子容器和子容器创建的子容器是平级的

  • 单例模式 Singleton 保存在根容器

  • 作用域模式 Scoped 保存在当前容器

  • 瞬时模式 Transient 不保存,一次性的


首先准备三个类并继承同一个基类,基类构造函数中打印对象信息

    public class Base    {        public Base() => Console.WriteLine($"Created:{GetType().Name}");    }
public interface ILogger { } public class Logger : Base, ILogger { }
public interface IServer { } public class Server : Base, IServer { }
public interface IHttp { } public class Http : Base, IHttp { }
复制代码


通过 ServiceCollection 对象 Build 的 容器 就是根容器,即这里的 root

在这里创建根容器以后,再通过根容器的 CreateScope 方法创建两个服务范围(作用域)

通过访问服务范围的 ServiceProvider 属性,可以拿到服务范围对应的子容器

最后我们通过不同的子容器连续两次创建不同的生命周期的实例

    var root = new ServiceCollection()        .AddTransient<ILogger, Logger>()        .AddScoped<IServer, Server>()        .AddSingleton<IHttp, Http>()        .BuildServiceProvider();        var child1 = root.CreateScope().ServiceProvider;    var child2 = root.CreateScope().ServiceProvider;        child1.GetService<ILogger>();    child1.GetService<ILogger>();    child1.GetService<IServer>();    child1.GetService<IServer>();    child1.GetService<IHttp>();    child1.GetService<IHttp>();    Console.WriteLine();    child2.GetService<ILogger>();    child2.GetService<ILogger>();    child2.GetService<IServer>();    child2.GetService<IServer>();    child2.GetService<IHttp>();    child2.GetService<IHttp>();
复制代码


上面代码运行得到的结果为如下

根据结果我们可以得知

生命周期注册为 Transient 时,每次通过容器创建对象时都是全新的对象

生命周期注册为 Scoped 时,同一个子容器中只会创建一次

生命周期注册为 Singleton 时,不同的子容器只能创建一次并复用

> Created:Logger> Created:Logger> Created:Server> Created:Http
> Created:Logger> Created:Logger> Created:Server
复制代码

容器创建的实例释放的策略

首先准备三个类并继承同一个基类,且基类实现 IDisposable

    public class Base : IDisposable    {        public Base() => Console.WriteLine($"Created:{GetType().Name}");
public void Dispose() => Console.WriteLine($"Disposed: {GetType().Name}"); }
public interface ILogger { } public class Logger : Base, ILogger { }
public interface IServer { } public class Server : Base, IServer { }
public interface IHttp { } public class Http : Base, IHttp { }
复制代码


通过 using 释放容器,打印日志查看对象释放点

    var collection = new ServiceCollection()       .AddTransient<ILogger, Logger>()       .AddScoped<IServer, Server>()       .AddSingleton<IHttp, Http>();        using (ServiceProvider root = collection.BuildServiceProvider())    {        using (IServiceScope scope = root.CreateScope())        {            var child = scope.ServiceProvider;            var logger = child.GetService<ILogger>();            var server = child.GetService<IServer>();            var http = child.GetService<IHttp>();            Console.WriteLine("子容器释放");        }        Console.WriteLine("根容器释放");    }
复制代码


上面代码运行得到的结果为如下

可以知道在容器释放对应生命周期的对象也会释放

> Created:Logger> Created:Server> Created:Http> 子容器释放> Disposed: Server> Disposed: Logger> 根容器释放> Disposed: Http
复制代码

多个构造函数时容器的选择策略

首先准备四个类,其中 User 不会注册到容器中,Server 类中有三个构造函数

    public interface ILogger { }    public class Logger : ILogger { }
public interface IHttp { } public class Http : IHttp { }
public interface IUser { } public class User : IUser { }
public interface IServer { } public class Server : IServer { public Server(ILogger logger) => Console.WriteLine($"Ctor(ILogger)");
public Server(ILogger logger, IHttp http) => Console.WriteLine($"Ctor(ILogger, IHttp)");
public Server(ILogger logger, IHttp http, IUser user) => Console.WriteLine($"Ctor(ILogger, IHttp, IUser)"); }
复制代码


注册 Logger,Http,Server,不注册 User

Build 出容器以后,使用容器创建类型为 Server 的对象

    var root = new ServiceCollection()        .AddScoped<ILogger, Logger>()        .AddScoped<IHttp, Http>()        .AddScoped<IServer, Server>()        .BuildServiceProvider();        root.GetService<IServer>();
复制代码


上面的代码得到的结果为如下

可以得到结论

  • 容器会提供构造函数所需要的所有参数,如果某个构造函数的参数类型集合,是所有合法构造函数参数类型集合的超集,那么这个构造函数就会被容器选择

  • 如果不存在某个构造函数的参数类型集合是所有合法构造函数参数类型集合的超集,并且有至少两个构造函数参数类型集合之间互存在差集,那么程序将会出现异常

> Ctor(ILogger, IHttp)
复制代码

参考文档

dotnet / runtime - Microsoft.Extensions.DependencyInjection

发布于: 刚刚阅读数: 6
用户头像

雄鹿 @

关注

心像开满花的树。 2019-01-04 加入

一名全栈开发工程师,热爱编程,对新技术充满好奇心,专注于使用ASP.NET Core和Angular进行Web应用的开发。

评论

发布
暂无评论
关于 ASP.NET Core 内置的依赖注入_ASP.NET Core_雄鹿 @_InfoQ写作社区