关于 Microsoft Extension: DependencyInjection 的介绍已经不少,可是多数偏重于实现原理和一些特定的实现场景。做为 dotnet core 的核心基石,这里准备全面介绍它的概念、原理和使用。git
这里首先介绍概念部分。github
该项目在 GitHub 的地址:https://github.com/aspnet/Extensions/tree/master/src/DependencyInjectionweb
Microsoft.Extensions.DependencyInjection
是微软对依赖倒置原则的实现。做为 ASP.NET Core 的基石,DependencyInjection
贯穿了整个项目的方方面面,掌握它的使用方式和原理,不只对理解 ASP.NET Core 有重要意义,也有助于将它运用到其它项目的开发中,帮助提供项目开发的效率和质量。编程
在软件开发中,项目一般有多个不一样的模块组成,模块之间存在依赖关系。例如,咱们考虑一个简化的场景,咱们有 3 个关于用户的类:设计模式
AccountController,提供用户交互界面框架
UserService,提供用户管理的业务逻辑ide
UserRepository,提供用户管理的数据访问函数
AccountController
内部须要使用 UserService
的实例 来管理用户,而 UserService
内部则须要基于 UserRepository
来提供数据访问。咱们称它们之间存在依赖关系。或者表达为,AccountController
依赖于 UserService
,而 UserService
依赖于 UserRepository
。而依赖注入就是用来帮助咱们实现依赖管理的有力工具。工具
依赖倒置原则是广为人知的设计原则之一,该原则是实现软件项目中模块的解耦的理论基石。测试
原则的定义以下:
High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
翻译过来为:
高层模块不该该依赖低层模块,二者都应该依赖抽象
抽象不该该依赖细节
细节应该依赖抽象
在没有实现依赖倒置原则的时候,咱们经过在 AccountController
类中本身经过 new
关键字来建立其依赖的 UserService
对象实例,
public class AccountController { private readonly UserService _userService; public AccountController() { this._userService = new UserService(); } }
这致使了两个类之间的紧耦合,AccountController
与 UserService
被绑定到一块儿, 在每次建立 AccountController
的时候,必定会建立一个 UserService
的对象实例,而若是咱们须要测试 AccountController
的时候,也就不得不考虑 UserService
,这样一级一级的依赖下来,UserService
又会依赖 UserRepository
,就会发现项目中的类都被绑定在一块儿, 紧密耦合,难以分拆。
基于依赖倒置的原则,一般会考虑经过接口进行隔离。例如,咱们可能会定义一个用户服务的接口:
public interface IUserService { }
而用户服务则会实现该接口
public class UserService : IUserService { }
在 AccountController
类中,则改变成了基于接口来使用 UserService
。
public class AccountController { private readonly IUserService _userService; public AccountController() { this._userService = new UserService(); } }
虽然在 HomeController
内部,咱们能够基于接口编程了,可是这样的做法并无解决本身经过 new
来获取 UserService
对象实例的问题。
IoC
是一种著名的实现 DIP 的设计模式。
它的核心思想是:在须要对象实例的时候,不要总考虑本身经过 new
来建立对象,放下依赖对象的建立过程,而是把建立对象的工做交给别人来负责,这个别人咱们一般称为 容器 (Container) 或者 服务提供者 (ServiceProvider), 咱们后面使用这个 ServiceProvider
来指代它,
在须要对象实例的时候,从这个 ServiceProvider
中获取。
下面是一个普遍使用的示意图。拿老是要拿的,可是从 本身穿上 变成了 给你穿上
在控制反转中,引入了一个 ServiceProvider 来帮助咱们得到对象实例。
DI 是 IoC 模式的一种实现。
《Expert one on one J2EE Development without EJB》第 6 章
IoC 的主要实现方式有两种:依赖查找,依赖注入 (p128)
依赖注入是一种更可取的方式。(p130)
Martin Fowler 的原文:
As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.
大意是:
已经存在某种模式,该模式被称为 IoC,但 IoC 太过广义,任何框架都 IoC,为了让表意更明确,决定采用 DI 来精确指称它。
DI 的实现有多种,咱们这里介绍的是微软官方在 Microsoft Extension 中内置提供的 DependencyInjection。它是 IoC 中一种实现,ASP.NET Core 的整个核心基于它来实现。同时,咱们也能够在其它项目中使用,以实现对依赖倒置原则的支持。
在微软的 DI 实现中,全部的服务须要首先注册到一个公共的服务描述集合中,该集合对于整个 DI 来讲,只须要一个,服务只须要在此集合中注册一次,便可在之后经过 DI 提供给使用者。
该集合的接口定义为 IServiceCollection
,能够看出来,它其实就是一个用来保存服务注册的集合。
public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable { }
系统默认已经实现了一个对 IServiceCollection
的实现,名为 ServiceCollection
。在 ASP.NET Core 中,内部会建立该对象的实例,咱们也能够在其它项目中,本身来建立它,很简单,直接 new
出来就可使用了。
IServiceCollection services = new ServiceCollection ();
在 ASP.NET Core MVC 中,你可能已经见过它了,不须要你来建立,系统已经帮你作了。
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IRepository, MemoryRepository>(); services.AddMvc(); }
在 DI 语境中,服务特指经过 DI 容器管理的对象实例。这个服务并不必定被称为 **Service,而是能够是任何由 DI 所管理的对象,只是在 DI 这个语境下,咱们将其统称为服务。
服务是咱们本身定义的,例如前面提到的 AccountController
和 UserService
等等。
咱们经过 DI 来得到服务对象实例,管理服务对象的生命周期,对于存在复杂依赖关系的对象, DI 还负责管理这些实例之间的依赖关系。
服务必须首先注册在 DI 中才能使用,可是,注册前须要首先考虑和决定服务的生命周期。
服务对象实例有着不一样类型的生命周期。有些对象的生命周期与应用程序相同,在应用程序启动时建立,在应用程序退出时才须要释放。例如咱们的数据访问对象实例。有些对象仅仅在当前方法中使用,在方法调用结束以后就应该销毁。服务的生命周期管理用来管理这些需求。
DI 支持三种类型的生命周期:
Singleton,单例,在当前应用程序环境下只有一个实例。例如数据访问服务对象实例。
Scoped,限定范围,一旦退出此范围,在此范围内的服务对象都须要销毁。例如 Web 开发中的请求对象实例。
Transient,瞬态,一次性使用,每次从 DI 中获取,都返回一个新的实例。
Microsoft.Extensions.DependencyInjection.ServiceLifetime
public enum ServiceLifetime { Singleton, Scoped, Transient }
服务的生命周期在注册服务的时候肯定。在使用的时候,直接获取实例,再也不指定服务的生命周期。微软提供了多种扩展方法来便于在注册服务时指定服务的生命周期。例以下面是经过泛型方式来指定单例模式的生命周期。
// 基于接口的注册 services.AddSingleton<IUserService, UserService>();
在须要使用服务对象实例的时候,不是从注册服务的集合中获取,而是须要经过服务提供者来获取,这个服务提供者显然须要来自注册服务的集合。服务提供者的接口定义为 IServiceProvider
,它是 .net 的基础定义之一,不是在该 DI 框架中定义的。
public interface IServiceProvider { object GetService(Type serviceType); }
DI 中的 ServiceCollectionContainerBuilderExtensions
扩展了 IServiceCollection
,提供了得到这个服务提供者 ServiceProvider 的支持。
public static ServiceProvider BuildServiceProvider(this IServiceCollection services) { return BuildServiceProvider(services, ServiceProviderOptions.Default); }
因此,咱们一般使用该方法来获取并使用它。
// 建立注册服务的容器 IServiceCollection services = new ServiceCollection (); // 注册服务,这里指定了单例 services.AddSingleton<IUserService, UserService>(); // 经过容器得到服务提供者 IServiceProvider provider = services.BuildServiceProvider ();
经过服务提供者来手动获取服务对象实例。经过注册的服务类型,直接调用 GetService
方法便可。
例如,前面咱们注册了服务类型 IUserService
的实现类型是 UserService
,那么,能够经过此类型来获取实际实现该接口的对象实例。
// 建立注册服务的容器 IServiceCollection services = new ServiceCollection (); // 注册服务,这里指定了单例 services.AddSingleton<IUserService, UserService>(); // 经过容器得到服务提供者 IServiceProvider provider = services.BuildServiceProvider (); // 经过接口获取服务对象实例 IUserService instance = provider.GetService<IUserService> ();
看起来,更加复杂了。在实际使用中,咱们不多使用这样的方式来使用 DI,后面咱们再深刻讨论具体的使用过程。
DI 支持构造函数注入。
定义 IUserRepository
接口,并实现 UserRepository
。
public interface IUserRepository { } public class UserReposotory: IUserRepository { }
UserService
经过构造函数依赖 IUserRepository
。
public class UserService : IUserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { this._userRepository = userRepository; } }
在经过 DI 得到 UserService
实例的时候,DI 帮助实例化其所依赖的 UserRepository
实例。
在 UserService
IServiceCollection services = new ServiceCollection (); // 基于接口的注册 services.AddSingleton<IUserService, UserService>(); services.AddSingleton<IUserRepository, UserReposotory>(); IServiceProvider provider = services.BuildServiceProvider (); IUserService instance = provider.GetService<IUserService> ();