.Net 5 DependencyInjection 依赖注入

依赖注入(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>();
            });
}
上一篇:SqlServer存储过程


下一篇:Sql语句注册公司的事务