NetCore 入门 (一) : 依赖注入

1. QuickStart

1.1 安装NuGet包

Microsoft.Extensions.DependencyInjection.Abstractions; // 抽象依赖包
Microsoft.Extensions.DependencyInjection; // 具体实现包

::: tip 包的设计原则
.Net Core提供的很多基础组件在设计时都会考虑“ 抽象和实现 ”的分离。例如要开发一款名为Foobar的组件,微软会将一些接口和必要的类型定义在NuGet包FooBar.Abstractions中,而将具体的实现类型定义在NuGet包FooBar中。如果另一个组件或框架需要使用FooBar组件,或需要对FooBar组件进行扩展与定制,只需要添加包FooBar.Abstractions即可。这种做法体现了“ 最小依赖 ”的设计原则。
:::

1.2 类型定义

public interface IFoo { }
public interface IBar { }
public interface IBaz { }
public class Foo : IFoo { }
public class Bar : IBar { }
public class Baz : IBaz { }

1.3 服务注册与消费

// 1. 创建服务集合
IServiceCollection services = new ServiceCollection();

// 2. 服务注册
// 有Singleton、Scoped、Transient三种生命周期
services.AddSingleton<IFoo, Foo>();
services.AddScoped<IBar, Bar>();
services.AddTransient<IBaz, Baz>();

// 3. 构建ServiceProvider
var provider = services.BuildServiceProvider();

// 4、 服务消费并验证
Debug.Assert(provider.GetService<IFoo>() is Foo);
Debug.Assert(provider.GetService<IBar>() is Bar);
Debug.Assert(provider.GetService<IBaz>() is Baz);

2. 服务注册

模型

这是依赖注入服务最核心的2个类:

2.1 服务的注册方式

可以通过以下三种方式进行服务的注册:

  • 指定服务的实现类型
  • 指定服务的实现实例
  • 指定服务的创建工厂
// 服务的实现类型
services.AddSingleton<IFoo, Foo>();

// 服务的实现实例
services.AddSingleton<IFoo>(new Foo());

// 服务的创建工厂
services.AddSingleton<IFoo>( _ => new Foo());

2.2 ServiceDescriptor

ServiceDescriptor对象用来描述服务的注册信息。在前面的示例中,services注册了不同生命周期的服务类型。本质上就是把服务注册的信息构建成了ServiceDescriptor对象。

public class ServiceDescriptor
{
    public Type ServiceType { get; }
    public ServiceLifetime Lifetime { get; }

    public Type ImplementationType { get; }
    public object ImplementationInstance { get; }
    public Func<IServiceProvider, object> ImplementationFactory { get; }
}
  • ServiceType服务的注册类型
  • Lifetime服务的生命周期
  • ImplementationType服务的实现类型
  • ImplementationInstance服务的实现实例
  • ImplementationFactory服务的创建工厂

构造函数

可以通过构造函数来创建 ServiceDescriptor 对象。

public class ServiceDescriptor
{
    // 未指定生命周期,默认采用Singleton
    public ServiceDescriptor(Type serviceType, object instance); 
    public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
    public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
}

Describe方法

ServiceDescriptor提供了2个名为Describe的静态方法来创建ServiceDescriptor对象。

public class ServiceDescriptor
{
    public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, 
        ServiceLifetime lifetime);
    public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}

简化方法

以上两种方式都需要指定生命周期。为了简化对象的创建方式,ServiceDescriptor定义了一系列针对生命周期的静态生命周期。下面展示针对Singleton的方法。针对ScopedTransient也具有类似的定义。

public class ServiceDescriptor
{
    public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance);
    public static ServiceDescriptor Singleton(Type service, Type implementationType);
    public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);

    public static ServiceDescriptor Singleton<TService>(TService implementationInstance);
    public static ServiceDescriptor Singleton<TService, TImplementation>();
    public static ServiceDescriptor Singleton<TService>(Func<IServiceProvider, TService> implementationFactory);
    public static ServiceDescriptor Singleton<TService, TImplementation>(
        Func<IServiceProvider, TImplementation> implementationFactory
    );
}

2.3 IServiceCollection

IServiceCollection对象本质上是ServiceDescriptor的列表,针对服务的注册就是创建ServiceDescriptor对象并添加到IServiceCollection集合中的过程。

// IServiceCollection 接口
public interface IServiceCollection : IList<ServiceDescriptor> { }
public class ServiceCollection : IServiceCollection { }

Add

考虑到服务注册是一个高频操作,IServiceCollection接口提供了一系列扩展方法。如下2个Add方法将指定的一个或多个ServiceDescriptor添加到集合中。

public static class ServiceCollectionDescriptorExtensions
{
    public static IServiceCollection Add(this IServiceCollection collection,ServiceDescriptor descriptor);
    public static IServiceCollection Add(this IServiceCollection collection,IEnumerable<ServiceDescriptor> descriptors);
}

同时提供了针对生命周期的注册方法:

public static class ServiceCollectionServiceExtensions
{
    public static IServiceCollection AddScoped<TService>(this IServiceCollection collection);
    ...
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection collection);
    ...
    public static IServiceCollection AddTransient<TService>(this IServiceCollection collection);
    ...
}

Add类方法允许针对同一个服务类型添加多个ServiceDescriptor对象。在调用GetService<T>获取服务时,依赖注入容器会根据最近添加的ServiceDescriptor来创建服务实例。也就是说,多次进行服务注册,后者会把前者覆盖掉。

TryAdd

在大多数情况下,同一个服务类型存在一个ServiceDescriptor对象就够了。TryAdd方法会在注册前,根据服务的注册类型判断对应的ServiceDescriptor是否存在,即服务的存在性检验。只有在不存在的情况下,才会进行服务注册。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAdd(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static void TryAdd(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);

    public static void TryAddSingleton<TService, TImplementation>(this IServiceCollection collection);
    ...
    public static void TryAddScoped<TService, TImplementation>(this IServiceCollection collection);
    ...
    public static void TryAddTransient<TService, TImplementation>(this IServiceCollection collection);
    ...
}

TryAddEnumerable

IServiceCollection接口还具有如下两个名为TryAddEnumerable的方法。与TryAdd方法一样,TryAddEnumerable也会做ServiceDescriptor的存在性检验。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAddEnumerable(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static void TryAddEnumerable(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}

二者的区别在于:

  • TryAdd只根据 注册类型 判断服务注册是否存在。
  • TryAddEnumerable要同时根据 注册类型实现类型 来判断服务注册是否存在。

也就是说,对于每个注册类型,使用TryAdd,集合中只允许存在一个实现类型;而使用TryAddEnumerable,允许存在多个不同的实现类型

Factory 和 Lambda

::: danger
创建工厂的返回类型(即服务的实现类型),将被用于判断ServiceDescriptor的存在性。如果返回类型是object,会因为实现类型不明确而抛出异常ArgumentException
:::

// 实例工厂
Func<IServiceProvider, Foo> factory= _ => new Foo(); // 返回类型为Foo
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoo>(factory));

// lambda表达式
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoo>( _ => new Foo())); // 返回类型为object,抛出异常ArgumentException

对于lambda表达式的实例工厂,本质是一个Func<IServiceProvider, object>对象,此时会因为实现类型不明确而抛出异常ArgumentException

3. 服务消费

3.1 IServiceProvider

IServiceProvider接口作为服务提供者,定义了唯一的GetService方法,可以根据指定的类型来提供对应的服务实例。

public interface IServiceProvider
{
    object GetService(Type serviceType);
}

IServiceProvider还提供了如下几个扩展方法:

public static class ServiceProviderServiceExtensions{
    public static T GetService<T>(this IServiceProvider provider);

    public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
    public static T GetRequiredService<T>(this IServiceProvider provider);

    public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
    public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}
  • GetRequiredService

如果指定服务类型的服务注册不存在,GetService方法会返回null,而GetRequiredService方法会抛出InvalidOperationException异常。

  • GetServices

如果希望获取某一服务的所有实现类型,可以调用GetServices<T>方法,也可以这样调用:GetService<IEnumerable<T>>

3.2 构造函数选择原则

依赖注入服务提供了三种服务的注册方式:分别是指定服务的实现类型指定服务的实现实例指定服务的创建工厂。对于后两者,服务的实现实例很容易获得。对于第一种注册方式,
则需要调用该类型的构造函数。那么在存在多个构造函数的情况下,该如何创建服务的实现实例呢?

::: tip 原则1
候选构造函数具备一个基本的条件:IServiceProvider对象能够提供构造函数的所有参数。
:::

::: tip 原则2
每个候选构造函数的参数类型集合都是这个构造函数参数类型集合的子集。
:::

::: danger
如果无法通过以上2个原则确定最终的构造函数,系统将抛出异常InvalidOperationException
:::

示例演示

// 定义服务接口
public interface IFoo { }
public interface IBar { }
public interface IBaz { }
public interface IQux { }

// 定义服务实现类型
public class Foo : IFoo { }
public class Bar : IBar { }
public class Baz : IBaz { }
public class Qux : IQux
{
    public Qux(IFoo foo) => Console.WriteLine("Selected ctor: Qux(IFoo)");
    public Qux(IFoo foo, IBar bar) => Console.WriteLine("Selected ctor: Qux(IFoo, IBar)");
    public Qux(IFoo foo, IBar bar, IBaz baz) => Console.WriteLine("Selected ctor: Qux(IFoo, IBar, IBaz)");
}

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建服务集合
        IServiceCollection services = new ServiceCollection();

        // 2. 服务注册
        services.AddTransient<IFoo, Foo>();
        services.AddTransient<IBar, Bar>();
        services.AddTransient<IQux, Qux>();

        // 3. 构建ServiceProvider
        var provider = services.BuildServiceProvider();

        // 4. 获取服务实例
        provider.GetService<IQux>();

    }
}

对于定义在Qux中的3个构造函数来说,由于IServiceCollection集合包含IFoo接口和IBar接口的服务注册,所以能够提供前面2个构造函数的所有参数。第三个构造函数具有一个IBaz类型的参数,无法通过IServiceProvider对象来提供。

根据原则1, Qux的前两个构造函数成为合法的候选构造函数。

根据原则2, Qux的第二个构造函数的参数类型包含IFooIBar,而第一个构造函数只具有一个类型为IFoo的参数,所以最终选择的是第二个构造函数。

运行实例程序,输出结果如下所示:

Selected ctor: Qux(IFoo, IBar)

4. 生命周期

依赖注入框架(DI)支持三种生命周期模式:

public enum ServiceLifetime
{
    // Specifies that a single instance of the service will be created.
    Singleton = 0, 
    // Specifies that a new instance of the service will be created for each scope.
    // In ASP.NET Core applications a scope is created around each server request.
    Scoped = 1,
    // Specifies that a new instance of the service will be created every time it 
    // is requested.
    Transient = 2
}
  • Singleton: 单例模式,在应用程序的整个生命周期中仅存在唯一的一个实例。
  • Scoped: 作用域模式,在Scoped范围内存在唯一的一个实例。
  • Transient: 瞬态模式,在每次请求时,都将返回一个新的实例。

4.1 理解Scoped

SingletonTransient模式具有明确的语义,我们方便理解,但Scoped的生命周期就不是很好懂了。
看下Scoped的注解:

In ASP.NET Core applications a scope is created around each server request.

在ASP.NET Core应用程序中,服务器的每次请求都会创建一个scope对象。也就是说,Scoped作用域范围就是服务器的一次请求。服务器开始处理请求,一个Scoped对象被创建;请求处理结束,Scoped作用域结束,Scoped对象被销毁。

Scoped对象是由IServiceScope接口表示的服务范围, 该范围是由IServiceScopeFactory表示的服务范围工厂来创建的。

public interface IServiceScope : IDisposable
{
    IServiceProvider ServiceProvider { get; }
}


public interface IServiceScopeFactory
{
    IServiceScope CreateScope();
}

public static class ServiceProviderServiceExtensions
{
    public static IServiceScope CreateScope(this IServiceProvider provider)
        => provider.GetService<IServiceScopeFactory>().CreateScope();
}

任何一个IServiceProvider对象都可以利用其扩展方法CreateScope创建IServiceScope对象。从上面的定义可以看出,每个IServiceScope对象都包含一个IServiceProvider属性,该属性与创建它的IServiceProvider对象在逻辑上保持如下图所示的“父子关系”。

IServiceProvider 父子关系

但是从功能实现层面来说,DI并不需要维护这样的“父子关系”。IServiceProvider对象不需要知道自己的“父亲”是谁,它只关心作为根节点IServiceProvider对象在哪里。下图揭示了IServiceScopeIServiceProvider之间的引用关系。任何一个IServiceProvider对象都具有针对根容器的引用。

IServiceProvider 引用关系

Scoped使用示例

// 类型定义
public interface IFoo { }
public class S103_Foo : IFoo { }

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建服务集合
        IServiceCollection services = new ServiceCollection();

        // 2. 服务注册
        services.AddScoped<IFoo, S103_Foo>();

        // 3. 构建依赖注入容器
        var provider = services.BuildServiceProvider();

        // 4. CreateScope
        using (var scope = provider.CreateScope())
        {
            var foo = scope.ServiceProvider.GetService<IFoo>();
        };
    }
}

4.2 服务的提供

针对不同的生命周期,DI采用不同的策略来提供服务实例。

  • Singleton: 在第一次请求时创建该服务实例,并保存在作为根节点IServiceProvider对象中。这样保证了多个同根的IServiceProvider对象提供的 Singleton实例都是同一个对象。
  • Scoped: IServiceProvider对象创建的实例由自己保存。
  • Transient: 针对每次服务提供请求,IServiceProvider对象总是返回一个新的服务实例。

4.3 服务的释放

IServiceProvider还具有回收释放的作用。这里的回收释放与.Net Core自身的垃圾回收机制无关,仅仅针对实现了IDisposeIDisposeAsync接口的服务实例(下面称Disposable服务实例)。通过调用DisposeDisposeAsync方法,来完成服务实例的回收释放。

  • Singleton: Disposable服务实例被保存在作为根节点IServiceProvider对象上,只有这个根节点IServiceProvider对象被释放的时候,这些Disposable服务实例才会被释放。
  • ScopedTransient:Disposable服务实例被保存在创建它的IServiceProvider对象上,当这个对象释放的时候,这些Disposable服务实例就会被释放。

4.4 服务验证

如果某个Singleton服务依赖于一个Scoped服务,那么只有当Singleton服务实例释放的时候,被依赖的Scoped服务实例才能被释放。在这种情况下,Scoped服务实例变成了一个Singleton服务实例。

在ASP.NET Core应用中,将某个服务的生命周期设置为Scoped是希望依赖注入容器根据接收的每个请求来创建和释放服务实例。如果出现上述这种情况,Scoped服务实例引用的资源(数据库连接,IO等)就得不到及时释放,这无疑不是我们希望看到的结果。

在调用IServiceCollection接口的BuilderServiceProvider方法时,可以开启服务验证。

public static class ServiceCollectionContainerBuilderExtensions
{
    // 参数:
    //   validateScopes:
    //     true to perform check verifying that scoped services never gets resolved from
    //     root provider; otherwise false.
    //
    public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes);
    
    // 参数:
    //   options:
    //     Configures various service provider behaviors.
    //
    public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options);
}

public class ServiceProviderOptions
{
    //
    // 摘要:
    //     true to perform check verifying that scoped services never gets resolved from
    //     root provider; otherwise false. Defaults to false.
    public bool ValidateScopes { get; set; }
    //
    // 摘要:
    //     true to perform check verifying that all services can be created during
    //     BuildServiceProvider call; otherwise false. Defaults to false. NOTE: this check doesn't verify open
    //     generics services.
    public bool ValidateOnBuild { get; set; }
}

ValidateScopes

ValidateScopes 参数保证IServiceProvider对象不可能以单例形式提供Scoped服务。

示例:

public interface IFoo { }
public interface IBar { }

public class Foo : IFoo { }
public class Bar : IBar
{
    public Bar(IFoo foo) => Console.WriteLine("Bar Created");
}

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建服务集合
        IServiceCollection services = new ServiceCollection();

        // 2. 服务注册
        services.AddScoped<IFoo, Foo>();
        services.AddSingleton<IBar, Bar>();

        // 3. 构建ServiceProvider
        var provider = services.BuildServiceProvider(true);

        try
        {
            var bar = provider.GetService<IBar>();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error:{ex.Message}");
        }
    }
}

结果输出

Error:Cannot consume scoped service 'DependencyInjection.IFoo' 
from singleton 'DependencyInjection.IBar'.

ValidateOnBuild

如果ValidateOnBuild参数设置为true, 则IServiceProvider对象被构建的时候会检查每个注册服务是否都可以被正常创建。


public interface IBar { }
public class Bar : IBar
{
    private Bar() => Console.WriteLine("Bar Created");
}

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建服务集合
        IServiceCollection services = new ServiceCollection();

        // 2. 服务注册
        services.AddSingleton<IBar, Bar>();

        ServiceProviderOptions options = new ServiceProviderOptions
        {
            ValidateOnBuild = true
        };

        try
        {
            // 3. 构建ServiceProvider
            var provider = services.BuildServiceProvider(options);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error:{ex.Message}");
        }
    }
}

结果输出

Error:Some services are not able to be constructed (Error while validating the service descriptor 
'ServiceType: DependencyInjection.IBar Lifetime: Singleton ImplementationType: DependencyInjection.Bar': 
A suitable constructor for type 'DependencyInjection.Bar' could not be located. 
Ensure the type is concrete and services are registered for all parameters of a public constructor.)

5. 整合第三方框架

目前市场上还有很多开源的依赖注入框架,比较常用的有CastleStructureMapSpring.NETAutoFacUnityNinject等。如果开发者习惯使用这些第三方框架,可以借助DependencyInjection的整合方式。

DependencyInjection定义了使用依赖注入的基本方式:服务注册信息存储在IServiceCollection集合中,消费者通过IServiceProvider对象获得服务实例。如果要将第三方框架整合进来,就必须解决从IServiceCollection集合到IServiceProvider对象的适配问题。

5.1 IServiceProviderFactory

container

public interface IServiceProviderFactory<TContainerBuilder>
{
    TContainerBuilder CreateBuilder(IServiceCollection services);
    IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}

借助IServiceProviderFactory接口,将第三方框架整合进来:

  1. CreateBuilder:利用IServiceCollection集合创建针对某个框架的ContainerBuilder对象。
  2. CreateServiceProviderContainerBuilder对象提供创建IServiceProvider对象的方法。

5.2 DefaultServiceProviderFactory

这个是DependencyInjection框架内部的默认实现方式。

public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
    private readonly ServiceProviderOptions _options;

    public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default){}
    public DefaultServiceProviderFactory(ServiceProviderOptions options)
    {
        this._options = options;
    }

    public IServiceCollection CreateBuilder(IServiceCollection services)
    {
        return services;
    }

    public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
    {
        return containerBuilder.BuildServiceProvider(this._options);
    }
}

5.3 示例

// step 1: 提供 ServiceCollection
var services = new ServiceCollection()
    .AddSingleton<IFoo, Foo>()
    .AddSingleton<IBar, Bar>();

// step 2: 创建并配置TContainerBuilder
var factory = XxxServiceProviderFactory();
var builder = factory.CreateBuilder(services);

builder.optionXxx(...)
    .optionXxx(...)
    .optionXxx(...);

// step 3: 获得 IServiceProvider
IServiceProvider serviceProvider = factory.CreateServiceProvider(builder);
posted @ 2022-08-27 15:10  renzhsh  阅读(1152)  评论(0编辑  收藏  举报