.Net 5 DependencyInjection 依赖注入

  • A+
所属分类:.NET技术
摘要

依赖注入(Dependency Injection)简称DI,DI实现了控制反转(Inversion of Control,Ioc),遵循了依赖倒置原则,

依赖注入(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); 

AddSingletonAddScopedAddTransient是构建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()); } 

在上述代码中:

服务实例不是由服务容器创建的,框架不会自动释放服务,开发人员负责释放服务。

服务获取

GetRequiredServiceGetService区别

如果容器中不存在要获取的服务,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:创建没有在容器中注入的服务

构造函数可以使用没有在容器中注入的服务,但是参数必须分配默认值。

通过IServiceProviderActivatorUtilities解析服务时,构造函数注入需要公共构造函数

通过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>();             }); }