重庆熊猫 Loading

ASP.NET Core教程-Dependency Injection(依赖注入)

更新记录
转载请注明出处:
2022年11月17日 发布。
2022年11月16日 从笔记迁移到博客。

依赖注入基础

什么是依赖注入

依赖就是一个类型使用或引用到另一个类型,从严格意义上来讲,如果A类型的定义或实现中出现了B类型,就可以理解为A类型依赖了B类型,大体上有下列几种情形或者这些情形的组合:
A类型的构造函数入参包含了B类型。
A类型的属性、字段包含了B类型。
A类型的方法输入或输出参数包含了B类型。
A类型的方法的局部变量包含了B类型。

依赖注入简介(主要功能)(作用)

​ 应用程序是由多个组件构成的,组件与组件之间往往存在依赖关系。这种依赖关系是指两个不同组件之间的引用关系,当其中一方不存在时,另一方就不能正常工作,甚至不能独立存在。为了解决组件之间的依赖关系,可以使用依赖倒置原则(Dependency Inversion Principle)。这个原则指明,高层不应直接依赖低层具体实现,两者应均依赖抽象(或接口)。

image

为什么需要依赖注入

在面向对象编程思想中有一个很重要的原则叫单一职责原则,指一个类型应该仅负责单一的职责,当一个类型负责过多职责时,它被改变的因素就会增多,使类型变得更加不稳定。如前面所述,当A类型依赖B类型时,A类型并不关心B类型的实例是怎么产生的,A类型只在需要的地方使用B类型的属性或方法,例如一个司机(Driver类型)并不关心一辆汽车(Car类型)是怎么造出来的,他只关心这辆车是否可以被驾驶(Car类型的Run方法),当司机驾驶(调用Driver的Drive方法)汽车时,实际上是要让车跑起来(调用Car的Run方法),而不论我们给司机的是电机驱动的车还是汽油发动机驱动的车。

有一个对应的设计原则叫关注点分离原则,指在设计系统时应该在宏观上为不同的关注点分别设计,然后组合起来。这里的“关注点”可以理解为不同的能力,例如:对于MVC框架来说,Model负责数据的承载,View负责UI的渲染,Controller负责View、Model之间的数据传递以及通过仓储层将数据存储在数据库。这里Model的关注点是数据本身,View的关注点是UI渲染,Controller的关注点是传递和组合。

依赖倒置原则(DIP,Dependency Inversion Principle)

​ This is a software design principle represents the "D" in the SOLID principles of object-oriented programming It provides a guideline for avoiding a dependency risk and solving common dependency problems

控制反转原则 (IoC,Inversion of Control)

​ This is a technique that follows the DIP guidelines.This concept is the process of creating application components in a detached state preventing higher-level components from having direct access to lower-level components and allowing them to only interact via abstractions

依赖注入与控制反转的关系

依赖注入字面意思就是将B类型的实例注入给A类型。通常情况下,当我们讲依赖注入时,也会提到控制反转(Inversion of Control),它们本质上讲的是同一事物的两面,都是指处理对象之间的依赖关系。从容器的角度来看待,依赖注入由容器负责对象的构造,对象所依赖的其他对象由容器负责管理并注入给对象。控制反转是从对象的视角来讲的,指对象把构造自己及其依赖对象的控制权交给容器来管理

如果说工厂模式、抽象工厂模式是特定类型的创建过程的分离,那么依赖注入就是将任意类型的创建的能力分离出来,实际上在我们使用依赖注入框架时会惊奇地发现它实际上是支持工厂模式的。本质上,依赖注入是控制反转思想的一种实现方式。

在.NET Core生态中,是由依赖注入组件来实现依赖注入设计模式的,它的设计思路是通过定义类型的生命周期、构造方式等规则配置到容器中,由容器负责管理对象的创建和资源释放。

DI的好处(Benefits of DI)

  • 可以减少代码/组件间的耦合(loose coupling of components),面向接口约定,而不是面向实现。
  • 分离关注点(separation of concerns)。
  • 促进了组件的逻辑抽象(promotes the logical abstractions of components)。
  • 有助于单元测试(facilitates unit testing)(Testability)。
  • 提高可维护性(makes code maintenance manageable)。
  • 提高代码可读性(promotes clean and more readable code)。
  • 改变代码实现更加容易。
  • 延迟绑定(Late binding)。使用/切换服务很方便。
  • 支持并行开发(Parallel development)。开发组件不会存在直接影响。

依赖注入容器

当应用程序中有多处要用到依赖注入时,就需要一个专门的类来负责管理创建所需要的类并创建它所有可能要用到的依赖。这个类就是依赖注入容器(Dependency Injection Container)。也可以称之为控制反转容器(Inversion of Control Container,IoC容器)。

我们可以把依赖注入容器看作一个用于创建对象的工厂,负责向外提供被请求要创建的对象。当创建这个对象时,如果它又依赖了其他对象或服务,那么容器会负责在其内部查找需要的依赖,并创建这些依赖,直至所有的依赖项都创建完成后,最终返回被请求的对象。除了创建对象和它们的依赖之外,容器也负责管理所创建对象的生命周期。

.NET常见依赖注入容器

Autofac https://www.nuget.org/packages/Autofac/
LightInject https://www.nuget.org/packages/LightInject/
Unity https://www.nuget.org/packages/Unity/
SimpleInjector

ASP.NET Core中的依赖注入

说明

在 ASP.NET Core 中,所有被放入依赖注入容器的类型或组件称为服务(Service)。ASP.NET Core 框架内部集成依赖注入容器。
注意:除了使用内置的依赖注入容器还可以使用第三方依赖注入容器。

NuGet包

Microsoft.Extensions.DependencyInjection.Abstractions
Microsoft.Extensions.DependencyInjection

Microsoft.Extensions.DependencyInjection.Abstractions包含了依赖注入框架核心的接口定义,Microsoft.Extensions.DependencyInjection包含了框架的具体实现,这种将接口定义和具体实现放在不同包中的模式叫作接口实现分离模式,其好处是组件的使用者可以只依赖抽象定义包,可以在运行时决定使用哪种具体实现,这与依赖注入的核心思想是一致的。

DI在ASP.NET Core中位置

DI是 ASP.NET Core 所有其他功能的基础。

image

ASP.NET Core中DI的结构

依赖注入框架类型的主要命名空间为Microsoft.Extensions.DependencyInjection,其中核心的类型是ServiceDescriptor、ServiceCollection、ServiceProvider。

IServiceCollection: 领导制定的“物品购置及发放清单”。(服务集合)
ServiceDescriptor: 物品清单中的物品描述。(每个服务的描述)
IServiceProvider: 库管老张。(向外提供服务的类型)
IServiceScope: 维修小组。(请求服务的作用域)
GetService/GetServices/GetRequiredService/GetRequiredServices: 借工具的方法。(具体使用服务的方法)

image

ServiceDescriptor(服务描述类)

ServiceDescriptor类包含服务的类型、生命周期和构造方式等信息。ServiceCollection是ServiceDescriptor的集合,通过BuildServiceProvider方法可以获得服务容器ServiceProvider。通过ServiceProvider的GetService方法可以获得注册服务的实例。

public class ServiceDescriptor
{
    public ServiceLifetime Lifetime { get; }
    public Type ServiceType { get; }
    public Type ImplementationType { get; }
    public object ImplementationInstance { get; }
    public Func<IServiceProvider, object> ImplementationFactory { get; }
}

其中ServiceType表示要注册的类型,也就是将来要获取的实例的类型,既可以是接口、抽象类,也可以是普通的类型,例如自定义的类型Car。属性ImplementationType表示实际上要创建的类型,比如在继承自Car的子类RaceCar中,属性ImplementationInstance表示在注册时已经获得了一个RaceCar的实例并将它注册进来,将来要获取Car的实例时,将ImplementationInstance返回给调用者即可。属性ImplementationFactory表示注册了一个用于创建ServiceType指定的类型的工厂,当需要从容器获取Car时,由这个工厂负责创建Car的实例。ServiceLifetime Lifetime 服务的生命周期,ServiceLifetime Lifetime 是一个枚举值有:AddSingleton、AddScoped 和AddTransient。

可以有3种方式获得目标类型(ServiceType)的实例:·从已有的实例来创建,即ImplementationInstance。从指定的类型来动态创建,即ImplementationType。从指定的工厂方法来动态创建,即ImplementationFactory。需要注意的是,对于一个ServiceDescriptor实例,只能为其注册这三种方式之一,它们是互斥的,不能同时定义。为了方便地创建ServiceDescriptor实例,提供了一组静态方法:

//创建瞬时服务注册实例
public static ServiceDescriptor Transient<TService, TImplementation>();
public static ServiceDescriptor Transient(Type service, Type implementationType);
public static ServiceDescriptor Transient<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory);
public static ServiceDescriptor Transient<TService>(Func<IServiceProvider, TService> implementationFactory);
public static ServiceDescriptor Transient(Type service, Func<IServiceProvider, object> implementationFactory);

//创建范围服务注册实例
public static ServiceDescriptor Scoped<TService, TImplementation>();
public static ServiceDescriptor Scoped(Type service, Type implementationType);
public static ServiceDescriptor Scoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory);
public static ServiceDescriptor Scoped<TService>(Func<IServiceProvider, TService> implementationFactory);
public static ServiceDescriptor Scoped(Type service, Func<IServiceProvider, object> implementationFactory);

//创建单例服务注册实例
public static ServiceDescriptor Singleton<TService, TImplementation>();
public static ServiceDescriptor Singleton(Type service, Type implementationType);
public static ServiceDescriptor Singleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory);
public static ServiceDescriptor Singleton<TService>(Func<IServiceProvider, TService> implementationFactory);
public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static ServiceDescriptor Singleton<TService>(TService implementationInstance);
public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance);

//创建指定生命周期注册实例
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime)

IServiceCollection

IServiceCollection是一个IList<ServiceDescriptor>类型的集合。ServiceDescriptor就是这个集合中的子项了,也就是“清单中物品的描述”。

public interface IServiceCollection : IList<ServiceDescriptor>
{
}

public class ServiceCollection : IServiceCollection
{
    // Some Code
}

最直接的方式就是创建ServiceDescriptor实例,并通Add方法添加到IServiceCollection中:

IServiceCollection services = new ServiceCollection();
var serviceDescriptor = ServiceDescriptor.Singleton<IMyService, MyService>();
services.Add(serviceDescriptor);

另外还提供了一组扩展方法来快速注册服务,并给出备注说明:

AddSingleton,将服务注册为单例的。

AddScoped,将服务注册为范围的。

AddTransient,将服务注册为瞬时的。

这些不同生命周期的服务注册方法都提供了多种重载,支持用户按类型、按实例、按工厂方法的方式来定义服务的构造方式:

IServiceCollection services = new ServiceCollection();

//指定实例
services.AddSingleton<IMyService>(new MyService());

//指定类型
services.AddSingleton<IMyService,MyService>();

//工厂模式
services.AddScoped<IMyService>(provider => new MyService());

//工厂模式,从容器中获取
services.AddTransient<IMyService>(provider => provider.GetService<MyService>());

需要注意的是,指定已有实例的方式仅支持单例模式。工厂方法模式使用了委托Func<IServiceProvider, object>,其入参是一个IServiceProvider,实际上就是当前的容器实例,因此可以如上面的代码那样从容器中取出需要的服务实例用于构造服务。

在实际场景中,我们通常会遇到一个服务被多个组件依赖的情形,仅当服务未被注册时才需要进行注册,可以使用TryAddxxx系列扩展方法,这些方法会先判断指定的服务是否已注册过:如果已经注册过,则忽略本次注册操作;如果未注册过,则注册该服务。

IServiceCollection services = new ServiceCollection();

//指定实例
services.TryAddSingleton<IMyService>(new MyService());

//指定类型
services.TryAddSingleton<IMyService,MyService>();

//工厂模式
services.TryAddScoped<IMyService>(provider => new MyService());

//工厂模式,从容器中获取
services.TryAddTransient<IMyService>(provider => provider.GetService<MyService>());

还有一种较特殊的场景,就是为服务注册不同的实现。要避免同一具体实现被重复注册,可以使用TryAddEnumerable方法。该方法有两个重载,一个是注册单个ServiceDescriptor,另一个是批量注册,传入IEnumerable<ServiceDescriptor>。下面的代码演示了这两种不同的方式:

IServiceCollection services = new ServiceCollection();

//为IMyService分别注册实现 MyService和MyServiceImpV2
services.TryAddEnumerable(ServiceDescriptor.Scoped<IMyService,MyService>());
services.TryAddEnumerable(ServiceDescriptor.Scoped<IMyService, MyServiceImpV2>());

//由于上面已经注册过IMyService的实现MyService,因此下面这两行代码不会产生任何效果
services.TryAddEnumerable(ServiceDescriptor.Scoped<IMyService>(p => new MyService()));
services.TryAddEnumerable(ServiceDescriptor.Transient<IMyService,MyService>());


//以集合的方式注册
IEnumerable<ServiceDescriptor> myServiceDescriptors = new List<ServiceDescriptor>()
{
    ServiceDescriptor.Scoped<IMyService,MyService>(),
    ServiceDescriptor.Scoped<IMyService,MyServiceImpV2>()
};
services.TryAddEnumerable(myServiceDescriptors);

IServiceProvider

IServiceProvider 服务提供者,即库管“老张”,作用就像老张一样,说明你需要什么服务的实例,并根据清单物品规格给你对应的工具。由IServiceCollection的扩展方法BuildServiceProvider创建,当需要它提供某个服务时,会根据创建它的IServiceCollection中对应的ServiceDescriptor 提供相应的服务实例。它提供了 GetService、GetRequiredService、GetServices、GetRequiredServices等用于提供服务实例的方法。

接口IServiceProvider位于System命名空间下,是一个有很长历史的接口,早在.NET Framework 1.1版本中就存在了,并且在很多针对特定场景的实现中,它的定义只有一个GetService方法,入参是服务的类型,返回值是服务的实例:

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

在依赖注入组件中,由类ServiceProvider实现接口IServiceProvider,它位于Microsoft.Extensions.DependencyInjection包中,同时类ServiceProviderServiceExtensions提供了一组扩展方法,让开发者可以更方便地编写获取实例的代码,尤其是泛型方法,它可以直接获得特定类型的返回值,而无须进行类型转换。

实例化服务容器

IServiceCollection services = new ServiceCollection();
// 这里省略了服务注册的代码
IServiceProvider serviceProvider = services.BuildServiceProvider(); 

获取服务实例

//指定类型获取服务实例
object myservice = serviceProvider.GetService(typeof(IMyService));

//泛型方法指定类型获取服务实例
IMyService myservice = serviceProvider.GetService<IMyService>();

如果要获取的服务类型没有注册在容器中,就返回null。如果我们期望在服务未被注册的情形下抛出异常而不是返回null,则可以调用GetRequiredService方法。如果服务未注册,则会抛出异常InvalidOperationException并显示服务未被注册。GetRequiredService方法在服务不可为空的场景中非常有用,有了该方法我们就无须重复地编写验证空服务的代码:

//指定类型获取服务实例
object myservice = serviceProvider.GetRequiredService(typeof(IMyService));

//泛型方法指定类型获取服务实例
IMyService myservice = serviceProvider.GetRequiredService<IMyService>();

在实际的应用程序中,当我们为同一类型重复注册时,GetService方法会以最后注册的生命周期和构造方法为准。我们也可以通过复数方法GetServices来获得一个实例集合,它对应了为同一类型的多个注册:

//指定类型,获取object实例集合
IEnumerable<object> myserviceList = serviceProvider.GetServices(typeof(IMyService));

//泛型方法,获取指定类型的实例集合
IEnumerable<T> myserviceList = serviceProvider.GetServices<T>();

IServiceScope

上文中的 ServiceDescriptor的Lifetime 为Scoped时,IServiceProvider会为其创建一个新的接口IServiceScope。根据服务描述中的不同生命周期实例化不同的作用域。

作用域与生命周期

Scope,字面意思就是范围,也就是作用域。Scope本身具有一个生命周期,就是从它被“创建”到它被“回收释放”这个周期。在Scope生存期间,我们将一些对象与其关联,当Scope回收释放时,同时也释放与其关联的对象,就能达到相应的效果,这些对象的生命周期与Scope相同,或者可以理解为这些对象的生命周期由Scope决定。

在依赖注入组件中,我们将由ServiceCollection通过BuildServiceProvider构造的IServiceProvider实例称作“根”,通常称为“根容器”,对应的依赖注入组件提供了一个接口IServiceScope来表示“子作用域”,我们可以通过扩展方法CreateScope来创建IServiceScope实例。下面的代码展示了IServiceScope的定义:

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

创建IServiceScope的方法

IServiceCollection services = new ServiceCollection();
// 这里省略了服务注册的代码
IServiceProvider serviceProvider = services.BuildServiceProvider();

//创建子容器
IServiceScope scope = serviceProvider.CreateScope();

IServiceScopeFactory的定义:

public interface IServiceScopeFactory
{
    IServiceScope CreateScope();
}

枚举ServiceLifetime

public enum ServiceLifetime
{
    Singleton,
    Scoped,
    Transient
}

Singleton表示单例,容器只会给生命周期为Singleton的类型创建一次实例,后续获取实例时都会返回这个唯一的实例。Singleton的服务只会由根容器创建,从子容器获取服务实例时会从根容器中查找。

Scoped表示范围,本质上是在作用域内的单例模式,在IServiceScope作用域内只给生命周期为Scoped的类型创建一次实例,后续在该作用域内都会获得同一实例。基于这个原则,根容器中的Scoped服务与Singleton服务是对等的。

Transient表示瞬时,是指每次通过容器获取对象都会创建一个新的实例,不论是在根容器中还是在子容器中。

根容器、子容器与不同生命周期服务实例间的关系

image

对于从根容器获得Scoped服务的情形,在BuildServiceProvider中可以使用validateScopes来决定行为:·false,允许从根容器获取Scoped服务,行为与Singleton服务一致。·true,不允许从根容器获取服务,如果尝试这样做,则抛出异常。

ASP.NET Core中DI所在命名空间

using Microsoft.Extensions.DependencyInjection;

ASP.NET Core DI 包官网

https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/

ASP.NET Core 服务类型

  • 框架自身提供的服务(Framework-provided services)。预定义的服务是ASP.NET Core框架的组成部分,会自动进行注册。部分服务也可以按需手动注册。如IApplicationBuilder、IHostingEnvironment和ILoggerFactory, Hosting, Configuration, Logging, HttpContext, and many others。
  • 应用程序级的服务(Application services)。这些服务通常由编码代码的人为应用创建的,需要自己去创建和手动注册服务(manually register)。

ASP.NET Core 服务的生命周期(dependency lifetimes)

说明

​ ASP.NET Core 支持依赖关系注入(DI)软件设计模式。该模式允许我们注册服务、控制如何实例化这些服务并将其注入到不同的组件中。一些服务可以在短周期内实例化,并且仅在特定的组件和请求中可用。一些实例仅被实例化一次,并在整个应用程序生命周期中可用。

在ASP.NET Core内置的依赖注入容器中服务的生命周期有如下3种类型:

  • Singleton:(单例服务,Singleton service)。创建服务类的单个实例,将其存储在内存中,并在整个应用程序中重复使用。容器会创建并共享服务的单例,且一直会存在于应用程序的整个生命周期内。
  • Transient:(瞬变服务,Transient service)。每次服务被请求时,总会创建新实例。
  • Scoped:(范围服务,Scoped service)。每个请求会创建一次服务实例,范围内不重新创建新实例。在每一次请求时会创建服务的新实例,并在这个请求内一直共享这个实例。参与处理单个请求的所有中间件、MVC 控制器等等,都将获得相同的实例。实体框架上下文(Entity Framework context)是使用 Scoped 服务的一个场景。

注意:

  • 当每次在容器中添加服务时,都需要指明其生命周期类型。
  • 当服务的生命周期结束时,它就会被销毁。

图解服务的生命周期的不同

注意:图中背景颜色(深浅)不同的 Instance 代表不同的服务实例。

image

image

image

单例服务(Singleton service)

说明

​ 单例服务在整个应用程序的依赖中只会创建一次,单例服务适合来实例化昂贵的服务。对象将存储在内存中,并将可重复用于应用程序中的所有注入对象将存储在内存中。典型的单例服务就是ILogger服务,这意味着在将ILogger 实例注入到Controller中时,每次使用的都是同一个对象。

实例

services.AddSingleton<IMusicManager, MusicManager>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));

作用域服务(Scoped service)

说明

​ Scoped生存期是在每个客户端请求的生存期中创建的服务。每个Web请求都会创建一个实例。通常用于ORM(ORM,Object Relational Mapper) ,比如Microsoft's Entity Framework Core (EF)。默认情况下每次Web请求都会创建一个新的DbContext,这是为了确保针对每个请求的处理数据的相关调用将包含在同一对象实例中。

实例

services.AddScoped<IMusicManager, MusicManager>();

瞬时服务(Transient service)

说明

每次请求时都会创建临时服务,不管它是一个新请求还是相同的请求。暂时服务的生命周期最适合轻量级和无状态服务,因为它们在请求结束时就被释放。使用IServiceCollection.AddTransient()方法进行注册暂时服务。

实例

services.AddTransient<IMusicManager, MusicManager>();
services.AddTransient<InstrumentalMusicManager>();

注册服务(Registering the service)

Add*()方法注册服务

​ 为了能够在程序中使用服务,首先需要向容器添加(注册)服务,然后通过DI注入所需要的类中。若要添加服务使用Startup.cs文件中Startup类的ConfigureServices()方法进行注入。该方法有一个IServiceCollection类型的参数services,它位于 Microsoft. Extensions.DependencyInjection 命名空间下。使用它就可以进行注册服务。

除了基本的 Add() 方法添加服务,还可以使用依赖注入扩展 Add**() 方法。
注意:Add方法和TryAdd方法的区别。当DI容器中已经有相同的服务时,TryAdd方法不会继续添加服务

命名空间

using Microsoft.Extensions.DependencyInjection.Extensions;

使用Add扩展方法添加和注册服务。

Add()
AddSingleton()
AddSingleton<T,U>()
AddScoped()
AddScoped<T,U>()
AddTransient()
AddTransient<T,U>()

使用TryAdd()扩展泛型方法(generic extension methods)添加和注册服务。

TryAdd()
TryAddSingleton()
TryAddSingleton<T,U>()
TryAddScoped()
TryAddScoped<T,U>()
TryAddTransient()
TryAddTransient<T,U>()

实例

public void ConfigureServices(IServiceCollection services)
{
    services.Add(new ServiceDescriptor(typeof(IBook), typeof(Book), ServiceLifetime.Scoped));
    services.TryAddSingleton<IMusicManager, AwesomeMusicManager>();
    services.AddScoped(typeof(IBook), typeof(Book));
	services.AddScoped<IBook, Book>();
}

框架自带的服务

框架自带的服务会自动进行注册,不需要手动注册。在需要的地方直接使用即可。比如:Hosting、Configuration、Logging。

image

ServiceDescriptor(服务描述器)

说明

​ 使用IServiceCollection的Add方法添加服务,本质是添加一个ServiceDescriptor对象到IServiceCollection中。事实上IServiceCollection就是一个ServiceDescriptor类型的集合,继承自Icollection类。ServiceDescriptor类描述一个服务、服务的具体实现、服务的生命周期。

​ 服务描述器包含有关已在DI容器中注册的注册服务的信息。包括服务类型,具体的实现和生命周期,一般情况下不需要直接对服务描述器操作,因为它们通常是由IServiceCollection的各种扩展方法自动创建的。ServiceDescriptor类的构造函数的参数分别是接口、实现的类型、生命周期。

主要有4种方式创建服务描述类型实例

第一种方式:直接实例化ServiceDescriptor类型

//直接指定实例化对象,使用这种方式服务的生命周期将是Singleton
public ServiceDescriptor(Type serviceType, object instance);
//以工厂的方式来创建实例,以满足更复杂的创建要求
public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);

实例:在Startup.cs文件中注入服务描述器

public void ConfigureServices(IServiceCollection services)
{
    var descriptor = new ServiceDescriptor(typeof(IHitCounterService),new HitCounterService(_rootPath));
    services.Add(descriptor);
}

实例:在Startup.cs文件中注入服务描述器

//实例化ServiceDescriptor类型
var serviceDescriptor = new ServiceDescriptor(
    typeof(IMusicManager),
    typeof(MusicManager),
    ServiceLifetime.Singleton);
//添加服务到DI容器种
services.Add(serviceDescriptor);

更多重载请查看官方文档:https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicedescriptor

第二种方式:使用ServiceDescriptor.Describe()静态方法

实例:

//实例化ServiceDescriptor类型
var serviceDescriptor = ServiceDescriptor.Describe(
    typeof(IMusicManager),
    typeof(MusicManager),
    ServiceLifetime.Singleton
);
//添加服务到DI容器种
services.Add(serviceDescriptor);

更多重载请查看官方文档
https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicedescriptor.describe

第三种方式:使用ServiceDescriptor.Singleton()静态方法

实例:

//实例化ServiceDescriptor类型
var serviceDescriptor = ServiceDescriptor.Singleton
(
    typeof(IMusicManager),
    typeof(MusicManager)
);
//添加服务到DI容器种
services.Add(serviceDescriptor);

实例:

var serviceDescriptor = ServiceDescriptor
                        .Singleton<IMusicManager,MusicManager>();
services.Add(serviceDescriptor);

常用的服务

对于一些常用的服务,如MVC、Entity Framework Core的DbContext等。IServiceCollection也提供了相应的扩展方法,能够将对应的服务更方便地添加到容器中,如AddMvc、AddDbContext和AddOptions等。

当服务添加到容器中后,就可以在程序中使用了。例如在Controller中或Startup类的Configure方法中

注入服务

说明

注册完服务后,就可以使用服务了。使用的方式有以下几种:构造函数注入、方法注入和通过HttpContext手工获取。此外,还可以通过IApplicationBuilder接口的ApplicationServices属性来访问服务,此属性的类型为IServiceProvider。另外需要注意的是,ASP.NET Core内置的容器默认支持构造函数注入。
注意:ASP.NET Core不支持属性注入

注入的方式类型(types of dependency injection)

说明

  • 构造器注入(Constructor injection)
  • 方法注入(Method injection)
  • 属性注入(Property injection)
  • 视图注入(View injection)
  • 在请求上下文中获得服务

构造器注入(Constructor Injection)

通过在构造函数传入所需要的依赖后,就可以将它保存为类级别的全局变量。也就可以在整个类中使用,构造函数注入遵循了显式依赖原则(Explicit Dependencies Principle)。

属性注入(Property injection)

属性注入是通过设置类的属性来获取所需要的依赖,只要在已经实例化的对象上设置相应的属性即可。它不像构造函数注入一样,只有显式地提供所有的依赖,才能创建指定的类。

存在问题:
由于为依赖项属性设置的值并非是强制的,因此很容易忘记设置,由此引发不必要的异常。

方法注入(Method injection)

方法注入是通过在方法的参数中传入所需要的依赖,如果类中的某一个方法需要依赖其他组件,则可以增加相应的参数。这个方法也应该为public类型。

视图注入(View injection)

使用@inject指令实现视图注入,@inject可以直接将服务注入到视图中。视图注入适合用在直接在视图中使用的逻辑的场景(view-specific services),比如本地化或者只用于视图的服务。

实例:

@inject WebApplication.Services.PandaService

如何选择构造函数注入还是方法注入

下述代码中,使用了构造函数注入,还使用了方法注入。方法注入需要注意:需要将注入的参数使用[FromService]注解修饰。如果一个服务仅在一个方法内使用,应使用方法注入。如果一个类的多个方法都会用到某个服务,则应该使用构造函数注入。

public class HomeController: Controller
{
    private readonly IDataService _dataService;

    //构造函数注入
    public HomeController(IDataService dataService)
    {
        _dataService = dataService;
    }

    //方法注入(记得带上[FromServices])
    [HttpGet]
    public IActionResult Index([FromServices]IDataService dataService2)
    {
        IDataService dataService = HttpContext.RequestServices. GetService<IDataService>();
        return View();
    }
}

构造函数注入

public class HomeController: Controller
{
    private readonly IDataService _dataService;

    //构造函数注入
    public HomeController(IDataService dataService)
    {
        _dataService = dataService;
    }
}

方法注入

注意:需要将注入的参数使用[FromService]注解修饰。

public class HomeController: Controller
{
    private readonly IDataService _dataService;

    //构造函数注入
    public HomeController(IDataService dataService)
    {
        _dataService = dataService;
    }

    //方法注入
    [HttpGet]
    public IActionResult Index([FromServices]IDataService dataService2)
    {
        IDataService dataService = HttpContext.RequestServices. GetService<IDataService>();
        return View();
    }
}

视图注入

使用@inject指令即可。

@inject PandaCMS.Web.Services.PandaSomeService IPandaService
@PandaService.DoSome()

通过HttpContext获得服务

​ 还可以通过HttpContext对象的RequestServices属性来获取服务,RequestServices属性的类型同样是IServiceProvider。它包括GetService()、GetRequiredService()以及它们各自的泛型重载。GetService()和GetRequiredService()的区别是当容器中不存在指定类型的服务时,前者会返回 null,而后者则会 抛出InvalidOperationException异常。

名称 描述
GetService() This method returns a service for the type specified by the generic type parameter or null if no such service has been defined.
GetService(type) This method returns a service for the type specified or null if no such service has been defined.
GetRequiredService() This method returns a service specified by the generic type parameter and throws an exception if a service isn’t available.
GetRequiredService(type) This method returns a service for the type specified and throws an exception if a service isn’t available.

实例:在中间件中使用注册后的服务

public class HttpMethodCheckMiddleware
{
    private readonly RequestDelegate _next;

    public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostingEnvironment environment)
    {
        this._next = requestDelegate;
    }

    public Task Invoke(HttpContext context)
    {
        //通过context.RequestServices.GetService获得服务
        IDataService dataService = context.RequestServices.GetService<IDataService>();
    }
}

通过ActivatorUtilities获得服务

名称 描述
CreateInstance(services, args) This method creates a new instance of the class specified by the type parameter, resolving dependencies using the services and additional (optional) arguments.
CreateInstance(services, type, args) This method creates a new instance of the class specified by the parameter, resolving dependencies using the services and additional (optional) arguments.
GetServiceOrCreateInstance(servic es, args) This method returns a service of the specified type, if one is available, or creates a new instance if there is no service.
GetServiceOrCreateInstance(services, type, args) This method returns a service of the specified type, if one is available, or creates a new instance if there is no service.

依赖注入容器(Dependency Injection Containers)

说明

The DI container manages the instantiation and configuration of the services registered。被依赖注入容器管理的依赖项(dependencies)叫做服务(services),当使用依赖注入容器可以简化依赖的管理包括服务的生命周期。

主要在Startup.cs的Startup类的ConfigureServices方法中添加服务到容器中

public void ConfigureServices(IServiceCollection services)
{
    services.Addxxxx();
}

DI容器的工作流程

注册服务(Registration)

在服务使用之前需要先注册服务,在Startup.cs文件中Startup类的ConfigureServices()方法中进行注册服务。

实例:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMusicManager, MusicManager>();
}

使用服务(Resolution)

在使用中我们会将依赖注入到使用的地方,在应用运行后,需要DI容器去进行解析。在应用程序启动时DI容器通过创建对象实例并将其注入到类中来自动解决依赖关系。

实例:

private readonly IMusicManager _musicManager;
public HomeController(IMusicManager musicManager)
{
    _musicManager = musicManager;
}

处置服务(Disposition)

通过检测服务的生命周期对其进行生命周期的管理。

一种服务多种实现(Dealing with multiple service implementations)

说明

可以在DI容器中为一种服务注册多种具体的实现,适合于一个服务对应多个具体实现的场景。

具体实现方法

使用TryAddEnumerable()扩展方法进行注册服务,然后使用的时候使用注入IEnumerable类型即可。

实例

使用TryAddEnumerable注册服务

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMusicManager,MusicManager>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMusicManager,AwesomeManager>());

然后在注入的地方使用枚举类型方式使用,使用时可以遍历枚举类型来确定想要的具体实现。

private readonly IEnumerable<IMusicManager> _musicManagers;
public HomeController(IEnumerable<IMusicManager> musicManagers)
{
    _musicManagers = musicManagers;
}

替换服务(Replacing service Registrations)

说明

使用Replace()扩展方法实现替换服务。
注意:Replace方法只会替换找到的第一个符合条件的服务。
注意:替换服务在具体的应用中并不常见,常用于重新实现框架的服务。

实例

services.AddSingleton<IMusicManager, MusicManager>();
services.Replace(ServiceDescriptor.Singleton<IMusicManager,AwesomeMusicManager>());

移除服务(removing service registrations)

说明

使用RemoveAll()扩展方法。
注意:移除服务在具体的应用中并不常见,常用于重新实现框架的服务。

实例

services.AddSingleton<IMusicManager, MusicManager>();
services.AddSingleton<IMusicManager, AwesomeMusicManager>();
services.RemoveAll<IMusicManager>();
posted @ 2022-11-17 10:58  重庆熊猫  阅读(964)  评论(0编辑  收藏  举报