本文章将介绍 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 内置的依赖注入框架 有个根容器和子容器的概念
首先准备三个类并继承同一个基类,基类构造函数中打印对象信息
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>();
复制代码
上面的代码得到的结果为如下
可以得到结论
参考文档
dotnet / runtime - Microsoft.Extensions.DependencyInjection
评论