- A+
依赖注入(Dependency Injection)简称DI,DI实现了控制反转(Inversion of Control,Ioc),遵循了依赖倒置原则,
DI实现解耦、不需要手动去获取或创建依赖的对象
控制反转:由容器帮我们控制对象的创建和依赖对象的注入
正转:直接获取依赖对象并手动创建对象
案例:
一些接口和类
public interface IStorage { } public class FileStorage : IStorage { public string Read(string path) { return File.ReadAllText(path); } } public interface IBookService { string[] GetBooks(); } public class BookService : IBookService { public IStorage Storage { get; } public BookService(IStorage storage) { Storage = storage; } public string[] GetBooks() { // ... return new string[] { }; } }
不使用依赖注入:
class Program { static void Main(string[] args) { // 需要创建或获取依赖 IStorage fileStorage = new FileStorage(); // 需要手动new服务并传入依赖 IBookService bookService = new BookService(fileStorage); bookService.GetBooks(); } }
使用依赖注入:
class Program { static void Main(string[] args) { // 创建依赖容器 IServiceCollection serviceCollection = new ServiceCollection(); // 注册服务 serviceCollection.AddSingleton<IStorage, FileStorage>(); // 注册服务 serviceCollection.AddSingleton<IBookService, BookService>(); // 构建服务提供者 IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); // 获取服务,IBookService的实现BookService的依赖将自动注入 IBookService bookService = serviceProvider.GetService<IBookService>(); bookService.GetBooks(); } }
服务注册
IServiceCollection
是一个ServiceDescriptor
服务描述器集合,ServiceDescriptor
描述了一个服务
public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable { }
注册服务就是向ServiceCollection
这个集合中添加ServiceDescriptor
IServiceCollection serviceCollection = new ServiceCollection(); var serviceDescriptor = new ServiceDescriptor( typeof(IStorage), // 服务类型 typeof(FileStorage), // 实现类型 ServiceLifetime.Transient // 生命周期 ); // 清除服务 serviceCollection.Clear(); // 是否包含服务 if (serviceCollection.Contains(serviceDescriptor)) { serviceCollection.Remove(serviceDescriptor); } // 注册服务 serviceCollection.Add(serviceDescriptor); // 只有容器中不存在此服务时才注册服务 serviceCollection.TryAdd(serviceDescriptor);
AddSingleton
、AddScoped
、AddTransient
是构建ServiceDescriptor
的简便扩展方法
IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<IStorage, FileStorage>(); serviceCollection.AddScoped<IStorage, FileStorage>(); serviceCollection.AddTransient<IStorage, FileStorage>(); serviceCollection.AddTransient<FileStorage>(); // 等同于 serviceCollection.AddTransient<FileStorage, FileStorage>()
在向容器注册服务时,可以填写 实现类型、工厂或者实例
IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.Add(new ServiceDescriptor(typeof(IStorage),typeof(FileStorage),ServiceLifetime.Transient)); FileStorage fs = new FileStorage(); serviceCollection.Add(new ServiceDescriptor(typeof(IStorage), fs)); serviceCollection.Add(new ServiceDescriptor(typeof(IStorage), serviceProvider => new FileStorage(), ServiceLifetime.Singleton));
方法 | 对象自动 dispose | 多种实现 | 转递参数 |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() 例子: services.AddSingleton<IMyDep, MyDep>() |
是 | 是 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) 例子: services.AddSingleton<IMyDep>(sp => new MyDep()) services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>() 例子: services.AddSingleton<MyDep>() |
是 | 否 | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) 例子: services.AddSingleton<IMyDep>(new MyDep()) services.AddSingleton<IMyDep>(new MyDep(99)) |
否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION}) 例子: services.AddSingleton(new MyDep()) services.AddSingleton(new MyDep(99)) |
否 | 否 | 是 |
不由服务容器创建的服务
考虑下列代码:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton(new Service1()); services.AddSingleton(new Service2()); }
在上述代码中:
服务实例不是由服务容器创建的,框架不会自动释放服务,开发人员负责释放服务。
服务获取
GetRequiredService
与GetService
区别
如果容器中不存在要获取的服务,GetRequiredService
将抛出异常,GetService
将返回null
使用IServiceProvider
延迟获取服务
案例:
class MyService6 { } class MyService5 { public IServiceProvider ServiceProvider { get; } public MyService5(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; } public void GetService6() { ServiceProvider.GetService<MyService6>(); } }
获取IEnumerable<>
服务数组
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<Animal, Dog>(); serviceCollection.AddSingleton<Animal, Cat>(); serviceCollection.AddSingleton<Animal, Pig>(); var serviceProvider = serviceCollection.BuildServiceProvider(true); var animals = serviceProvider.GetService<IEnumerable<Animal>>(); Console.WriteLine(animals.Count()); // 3
生命周期
有如下3种声明周期
- Transient:临时,每次都将创建一个实例
- Scoped:范围,作用域,对于 Web 应用程序,每次Http请求创建一个实例,也可以通过
CreateScope
创建一个作用域,在此作用域内只会创建一个实例 - Singleton:单例,只会创建一个实例
有作用域的服务由创建它们的容器释放
Transient
声明周期案例
class MyService : IDisposable { public MyService() { Console.WriteLine("MyService Construct"); // 创建一个新的实例将输出`MyService Construct` } public void Hello() { Console.WriteLine("MyService Hello"); } public void Dispose() { Console.WriteLine("MyService Dispose"); } }
C#2
var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient<MyService>(); var serviceProvider = serviceCollection.BuildServiceProvider(); serviceProvider.GetService<MyService>(); // 输出:MyService Construct serviceProvider.GetService<MyService>(); // 输出:MyService Construct serviceProvider.GetService<MyService>(); // 输出:MyService Construct
Scoped
声明周期案例
var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped<MyService>(); var serviceProvider = serviceCollection.BuildServiceProvider(); serviceProvider.GetService<MyService>(); // 输出:MyService Construct serviceProvider.GetService<MyService>(); // 无输出 serviceProvider.GetService<MyService>(); // 无输出 using (var serviceScope = serviceProvider.CreateScope()) { serviceScope.ServiceProvider.GetService<MyService>(); // 输出:MyService Construct serviceScope.ServiceProvider.GetService<MyService>(); // 无输出 serviceScope.ServiceProvider.GetService<MyService>(); // 无输出 } // 上面作用域结束后,将自动释放服务,输出 MyService Dispose
Single
声明周期案例
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<MyService>(); var serviceProvider = serviceCollection.BuildServiceProvider(); serviceProvider.GetService<MyService>(); // 输出:MyService Construct serviceProvider.GetService<MyService>(); // 无输出 serviceProvider.GetService<MyService>(); // 无输出 using (var serviceScope = serviceProvider.CreateScope()) { serviceScope.ServiceProvider.GetService<MyService>(); // 无输出 serviceScope.ServiceProvider.GetService<MyService>(); // 无输出 serviceScope.ServiceProvider.GetService<MyService>(); // 无输出 }
作用域验证
在调用BuildServiceProvider
时可以传入参数来配置是否启用作用域验证
对于Web应用程序,如果应用环境为“Development”(开发环境),默认作用域验证将启用(CreateDefaultBuilder
会将 ServiceProviderOptions.ValidateScopes
设为 true
),若要始终验证作用域(包括在生存环境中验证),请使用HostBuilder
上的 UseDefaultServiceProvider
配置 ServiceProviderOptions
启用作用域验证后,将验证以下内容:
- 确保没有从根服务提供程序直接或间接解析到有作用域的服务
- 未将有作用域的服务直接或间接注入到单一实例。
案例
class MyService2 { } class MyService3 { public MyService3(MyService2 myService2) { } }
C#2
var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped<MyService2>(); var serviceProvider = serviceCollection.BuildServiceProvider(true); // 传入true,开启作用域验证 using (var serviceScope = serviceProvider.CreateScope()) { serviceScope.ServiceProvider.GetService<MyService2>(); // 正确用法 } serviceProvider.GetService<MyService2>(); // 将抛出异常,因为不能从根服务提供程序直接或间接解析到有作用域的服务
C#3
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<MyService3>(); serviceCollection.AddScoped<MyService2>(); var serviceProvider = serviceCollection.BuildServiceProvider(true); serviceProvider.GetService<MyService3>(); // 将抛出异常,不能将有作用域的服务直接或间接注入到单一实例
调用 BuildServiceProvider
时,会创建根服务提供程序。 在启动提供程序和应用时,根服务提供程序的生存期对应于应用/服务的生存期,并在关闭应用时释放。
有作用域的服务由创建它们的容器释放
如果作用域创建于根容器,则该服务的生存会有效地提升至单一实例,因为根容器只会在应用/服务关闭时将其释放
构造函数注入行为
服务能被获取通过:
- IServiceProvider
- ActivatorUtilities:创建没有在容器中注入的服务
构造函数可以使用没有在容器中注入的服务,但是参数必须分配默认值。
通过IServiceProvider
或ActivatorUtilities
解析服务时,构造函数注入需要公共构造函数
通过ActivatorUtilities
解析服务时,构造函数注入要求仅存在一个适用的构造函数。 ActivatorUtilities
支持构造函数重载,其所有参数都可以通过依赖项注入来实现。
案例
class MyService4 { public MyService4() { Console.WriteLine("0 Parameter Constructor"); } public MyService4(string a) { Console.WriteLine("1 Parameter Constructor"); } public MyService4(string a, string b) { Console.WriteLine("2 Parameter Constructor"); } }
C#2
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<MyService4>(); var serviceProvider = serviceCollection.BuildServiceProvider(true); ActivatorUtilities.CreateInstance<MyService4>(serviceProvider); // 0 Parameter Constructor ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1"); // 1 Parameter Constructor ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", "Param 2"); // 2 Parameter Constructor ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", 12);// 抛出异常,没有找到合适的构造函数 ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", "Param 2", "Param 3");// 抛出异常,没有找到合适的构造函数
Asp.Net Core,注入 Startup 的服务
服务可以注入 Startup
构造函数和 Startup.Configure
方法
使用泛型主机 (IHostBuilder
) 时,只能将以下服务注入 Startup
构造函数:
- IWebHostEnvironment
- IHostEnvironment
- IConfiguration
任何向 DI 容器注册的服务都可以注入 Startup.Configure
方法:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger) { }
使用扩展方法注册服务组
ASP.NET Core
框架使用一种约定来注册一组相关服务。 约定使用单个 Add{GROUP_NAME}
扩展方法来注册该框架功能所需的所有服务。 例如,AddControllers
扩展方法会注册 MVC 控制器所需的服务
从 main 调用服务
使用 IServiceScopeFactory.CreateScope
创建 IServiceScope
以解析应用范围内的作用域服务。 此方法可以用于在启动时访问有作用域的服务以便运行初始化任务。
以下示例演示如何访问范围内 IMyDependency
服务并在 Program.Main
中调用其 WriteMessage
方法:
public class Program { public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); using (var serviceScope = host.Services.CreateScope()) { var services = serviceScope.ServiceProvider; try { var myDependency = services.GetRequiredService<IMyDependency>(); myDependency.WriteMessage("Call services from main"); } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "An error occurred."); } } host.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }