2020年12月5日星期六

.Net 5 DependencyInjection 依赖注入

  • .Net DependencyInjection 依赖注入
    • 服务注册
    • 不由服务容器创建的服务
    • 服务获取
    • 生命周期
    • 作用域验证
    • 构造函数注入行为
    • Asp.Net Core,注入 Startup 的服务
    • 使用扩展方法注册服务组
    • 从 main 调用服务

依赖注入(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 ConstructserviceProvider.GetService<MyService>(); // 输出:MyService ConstructserviceProvider.GetService<MyService>(); // 输出:MyService Construct

Scoped声明周期案例

var serviceCollection = new ServiceCollection();serviceCollection.AddScoped<MyService>();var serviceProvider = serviceCollection.BuildServiceProvider();serviceProvider.GetService<MyService>(); // 输出:MyService ConstructserviceProvider.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 ConstructserviceProvider.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 ConstructorActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1"); // 1 Parameter ConstructorActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", "Param 2"); // 2 Parameter ConstructorActivatorUtilities.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>();   });}








原文转载:http://www.shaoqun.com/a/494932.html

reverb:https://www.ikjzd.com/w/1273

haofang:https://www.ikjzd.com/w/1046

patents:https://www.ikjzd.com/w/857


.NetDependencyInjection依赖注入服务注册不由服务容器创建的服务服务获取生命周期作用域验证构造函数注入行为Asp.NetCore,注入Startup的服务使用扩展方法注册服务组从main调用服务依赖注入(DependencyInjection)简称DI,DI实现了控制反转(InversionofControl,Ioc),遵循了依赖倒置原则,DI实现解耦、不需要手动去获取或创建依
ola:ola
topia:topia
龙华汽车站到南澳西冲坐什么车?:龙华汽车站到南澳西冲坐什么车?
春节去珠海长隆人多吗?珠海长隆春节好玩吗?:春节去珠海长隆人多吗?珠海长隆春节好玩吗?
从罗湖过关到香港尖沙咀怎么走?:从罗湖过关到香港尖沙咀怎么走?

没有评论:

发表评论