關於 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> ();