ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

.Net 5 DependencyInjection 依赖注入

2020-12-05 03:32:24  阅读:238  来源: 互联网

标签:依赖 服务 serviceCollection serviceProvider new var DependencyInjection Net public


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

标签:依赖,服务,serviceCollection,serviceProvider,new,var,DependencyInjection,Net,public
来源: https://www.cnblogs.com/moeycy/p/DependencyInjection.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有