.NET Core ASP.NET Core Basic 1-2
本節(jié)內(nèi)容為控制反轉(zhuǎn)與依賴(lài)注入
簡(jiǎn)介
控制反轉(zhuǎn)IOC
這個(gè)內(nèi)容事實(shí)上在我們的C#高級(jí)篇就已經(jīng)有所講解,控制反轉(zhuǎn)是一種設(shè)計(jì)模式,你可以這樣理解控制反轉(zhuǎn),假設(shè)有一個(gè)人他有一部A品牌手機(jī),他用手機(jī)進(jìn)行聽(tīng)歌、打游戲,那么你可以創(chuàng)建一個(gè)手機(jī)類(lèi)和一個(gè)人類(lèi)
class APhone : IPhone { public string Owner{get;set;} public Phone(string own)
{ Owner = own; } void Play() { //省略 } void Music() { //省略 } } class Man {
public string Name{get;set;} void Game() { var p = new APhone(Name); p.Play();
} }
事實(shí)上這段代碼的耦合度是比較高的?它使用的是正轉(zhuǎn),也就是我需要什么東西的時(shí)候我就自己創(chuàng)建一個(gè)這個(gè)東西。為什么說(shuō)他不好呢,如果有一天這個(gè)人決定再也不使用A品牌手機(jī)了,他決定以后只使用B品牌。那么也就意味著整個(gè)的Man類(lèi)使用過(guò)APhone類(lèi)的地方都需要更改。這是一個(gè)非常麻煩的事情,我們這個(gè)時(shí)候就需要運(yùn)用我們的IOC控制反轉(zhuǎn)了。我們將實(shí)例或者是需要使用的對(duì)象的創(chuàng)建交給你的調(diào)用者,自己只負(fù)責(zé)使用,其它人丟給你依賴(lài)的這個(gè)過(guò)程理解為注入。
控制反轉(zhuǎn)的核心就是——原本我保存使用我自己的東西,現(xiàn)在我把控制權(quán)交給我的上級(jí),我需要使用的時(shí)候再向他要。這個(gè)時(shí)候,接口的作用不言而喻,A繼承了Phone接口,B也繼承了,假定我們一開(kāi)始就使用Phone接口去創(chuàng)建不同的A,B對(duì)象,那么是不是可以有效的切換AB對(duì)象呢?
依賴(lài)注入
依賴(lài)注入體現(xiàn)的是一個(gè)IOC(控制反轉(zhuǎn)),它非常的簡(jiǎn)單,我們之前的Man類(lèi)代碼中使用的是正轉(zhuǎn)的方式,也就是我要一個(gè)對(duì)象,那么我就創(chuàng)建一個(gè)?,F(xiàn)在我們使用依賴(lài)注入就是將我們對(duì)這個(gè)對(duì)象的控制權(quán)交給上一級(jí)接口,也就成為了這種,我想要一個(gè)對(duì)象,我就向上級(jí)發(fā)出請(qǐng)求,上級(jí)就給我創(chuàng)建了一個(gè)對(duì)象。我們通常使用構(gòu)造函數(shù)注入的方式進(jìn)行依賴(lài)的注入。
上文的代碼就會(huì)變成
class Man { private readonly IPhone _phone; public Man(IPhone phone) { _phone
= phone; } }
假設(shè)這個(gè)時(shí)候你需要將手機(jī)換成B品牌,那么只需要再注入的地方傳入B品牌的對(duì)象即可了。
容器
但是現(xiàn)在又出現(xiàn)了一個(gè)新的問(wèn)題,假設(shè)說(shuō)這個(gè)類(lèi)有100個(gè)使用該接口的依賴(lài),如果,我們是不是要在100個(gè)地方做這樣的事情?
控制是反轉(zhuǎn)了,依賴(lài)的創(chuàng)建也移交到了外部?,F(xiàn)在的問(wèn)題是依賴(lài)太多,我們需要一個(gè)地方統(tǒng)一管理系統(tǒng)中所有的依賴(lài),這個(gè)時(shí)候,我們就使用容器進(jìn)行集中的管理
容器負(fù)責(zé)兩件事情:
* 綁定服務(wù)與實(shí)例之間的關(guān)系
* 獲取實(shí)例,并對(duì)實(shí)例進(jìn)行管理(創(chuàng)建與銷(xiāo)毀)
使用
說(shuō)了那么多,我們?nèi)绾卧?NET Core中使用我們的依賴(lài)注入呢?這里我們針對(duì)的是所有的.NET Core的應(yīng)用,在.NET
Core中依賴(lài)注入的核心分為兩個(gè)組件:位于Microsoft.Extensions.DependencyInjection命名空間下的IServiceCollection和
IServiceProvider。
其中
* IServiceCollection 負(fù)責(zé)注冊(cè)
* IServiceProvider 負(fù)責(zé)提供實(shí)例
在默認(rèn)的容器ServiceCollection中有三個(gè)方法
* .AddTransient<I,C>()
* .AddSingleton<I,C>()
* .AddScoped<I,C>()
這里就不得不提一下我們依賴(lài)注入的三種生命周期了
* Singleton指的是單例模式,也就是說(shuō),在整個(gè)程序運(yùn)轉(zhuǎn)期間只會(huì)生成一次
* Transient指每一次GetService都會(huì)創(chuàng)建一個(gè)新的實(shí)例
* Scope指在同一個(gè)Scope內(nèi)只初始化一個(gè)實(shí)例 ,可以理解為( 每一個(gè)request級(jí)別只創(chuàng)建一個(gè)實(shí)例,同一個(gè)http request會(huì)在一個(gè)
scope內(nèi))
我們可以嘗試使用控制臺(tái)項(xiàng)目來(lái)模擬依賴(lài)注入的原理,也就是說(shuō)我們直接從容器獲取我們對(duì)象實(shí)例,并且我們使用Guid進(jìn)行唯一性的標(biāo)記。
//創(chuàng)建三個(gè)代表不同生命周期的接口 interface IPhoneScope { Guid Guid { get; } } interface
IPhoneSingleton { Guid Guid { get; } } interface IPhoneTransient { Guid Guid {
get; } } //實(shí)現(xiàn)的類(lèi) class PhoneService:IPhoneScope,IPhoneSingleton,IPhoneTransient
{ public PhoneService() { this._guid = Guid.NewGuid(); } public
PhoneService(Guid guid) { this._guid = guid; } private Guid _guid; public Guid
Guid => this._guid; }
然后,在我們的主函數(shù)中
namespace DI_AND_IOC { class Program { static void Main(string[] args) {
//注入服務(wù) var services = new ServiceCollection() .AddScoped<IPhoneScope,
PhoneService>() .AddTransient<IPhoneTransient, PhoneService>()
.AddSingleton<IPhoneSingleton, PhoneService>(); //構(gòu)造服務(wù) var provider =
services.BuildServiceProvider(); using (var scope = provider.CreateScope()) {
var p = scope.ServiceProvider; var scopeobj1 = p.GetService<IPhoneScope>(); var
transient1 = p.GetService<IPhoneTransient>(); var singleton1 =
p.GetService<IPhoneSingleton>(); var scopeobj2 = p.GetService<IPhoneScope>();
var transient2 = p.GetService<IPhoneTransient>(); var singleton2 =
p.GetService<IPhoneSingleton>(); Console.WriteLine( $"scope1:
{scopeobj1.Guid},\n" + $"transient1: {transient1.Guid}, \n" + $"singleton1:
{singleton1.Guid}\n"); Console.WriteLine($"scope2: {scopeobj2.Guid}, \n" +
$"transient2: {transient2.Guid},\n" + $"singleton2: {singleton2.Guid}\n"); }
//創(chuàng)建不同的scope using (var scope = provider.CreateScope()) { var p =
scope.ServiceProvider; var scopeobj3 = p.GetService<IPhoneScope>(); var
transient3 = p.GetService<IPhoneTransient>(); var singleton3 =
p.GetService<IPhoneSingleton>(); Console.WriteLine($"scope3: {scopeobj3.Guid},
\n" + $"transient3: {transient3.Guid},\n" + $"singleton3: {singleton3.Guid}");
} } } }
你應(yīng)該會(huì)得到類(lèi)似以下的數(shù)據(jù)
scope1: 096d38e5-0c7b-4e50-9c79-241fb18a56ed, transient1:
289ebd11-8159-4f22-b53e-ed738a317313, singleton1:
b453b7f5-3594-4b66-99c8-a72763abaa83 scope2:
096d38e5-0c7b-4e50-9c79-241fb18a56ed, transient2:
212ad420-e54c-4dd6-9214-abe91aacdd9c, singleton2:
b453b7f5-3594-4b66-99c8-a72763abaa83 scope3:
688b6ffd-a8c1-47f4-a20a-872c2285d67c, transient3:
3d09997d-fffb-43d1-9e53-ccf9771c819d, singleton3:
b453b7f5-3594-4b66-99c8-a72763abaa83
可以發(fā)現(xiàn),singleton對(duì)象是不會(huì)發(fā)生改變的,而scope對(duì)象在創(chuàng)建新的scope之后就發(fā)生了改變,而transient對(duì)象每一次請(qǐng)求都在發(fā)生改變。
需要注意的是,在控制臺(tái)項(xiàng)目使用容器服務(wù)需要引入 *** Microsoft.Extensions.DependencyInjection ***
程序集,你可以在引入中導(dǎo)入該dll
通過(guò)對(duì)注入服務(wù)的生命周期管控,在一些ASP.NET
Core項(xiàng)目中,有些類(lèi)(服務(wù))有可能跨越了多個(gè)Action或者Controller,那么我們正確的使用生命周期,我們可以盡可能的節(jié)省內(nèi)存,即能減少實(shí)例初始化的消耗。
在ASP.NET Core中的使用
在ASP.NET
Core中,我們使用依賴(lài)注入非常的簡(jiǎn)單,在StartUp類(lèi)中的ConfigureServices方法中已經(jīng)為我們構(gòu)建好了容器,我們只需要做類(lèi)似于這樣的操作
services.AddScoped<IPhoneScope, PhoneService>();
services.AddDbContext<DbContext>(); services.AddMVC();
如果你需要在控制器中注入服務(wù),官方的推薦方案是使用構(gòu)造函數(shù)注入
public IPhoneScope _ips; public Controller(IPhoneScope ips) { _ips = ips; }
特別的,你如果使用MVC的Razor頁(yè)面進(jìn)行注入的話(huà),那么輸入以下指令
@inject IPhoneScope ips
如果我的文章幫助了您,請(qǐng)您在github.NETCoreGuide項(xiàng)目幫我點(diǎn)一個(gè)star,在博客園中點(diǎn)一個(gè)關(guān)注和推薦。
Github <https://github.com/StevenEco/.NetCoreGuide>
BiliBili主頁(yè) <https://space.bilibili.com/33311288>
WarrenRyan'sBlog <https://blog.tity.xyz>
博客園 <https://cnblogs.com/warrenryan>
熱門(mén)工具 換一換
