ASP.NET CORE 內置的IOC解讀及使用

在我接觸IOC和DI 概念的時候是在2016年有幸倒騰Java的時候第一次接觸,當時對這兩個概念很是模糊;後來由於各種原因又回到.net 大本營,又再次接觸了IOC和DI,也算終於搞清楚了IOC和DI 這兩個概念關係。使用過ASP.NET Core的人對這兩個概念一定不陌生,想必很多人還是很難去理解這兩個東西,所以,趁著今天有空,就去把兩個概念捋清楚,並將學習過程的知識點記錄下來。

一、概念

1.1 什麼是IOC?

Ioc—Inversion of Control,即 控制反轉,其是一種 設計思想,而不是一種技術。再沒有使用IOC之前,我們一般是通過new來實例化,從而創建一個對象。但是我們使用IOC之後,創建這個對象的控制權將由內部轉換到外部,那麼這個過程便可以理解為控制反轉。也即 把對象轉換成抽象對象的依賴.。

同時控制反轉也是一個目標,控制反轉的優點有如下兩點:

  • 可以很好的做到 解耦
  • 屏蔽對象的實現細節,只關心動作不關心動作中的細節。

1.2 什麼是DI(依賴注入)?

全稱為 DependencyInjection,意思自身對象中的內置對象是通過注入的方式進行創建。形象的說,即由容器動態的將某個依賴關係注入到組件之中。

1.3 IOC和DI的聯繫?

IOC是一種設計思想,而DI是這種設計思想的一個實現。理解IOC和DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰注入誰,注入了什麼”。 ●誰依賴於誰:當然是應用程序依賴於IoC容器; ●為什麼需要依賴:應用程序需要IoC容器來提供對象需要的外部資源; ●誰注入誰:很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象; ●注入了什麼:就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)

1.4 常見的IOC框架。

微軟.net core 內置的DI、Autofac、Unity


以上已經把IOC和DI 這兩個聯繫簡要捋清楚了,下面我們一起學習.net core 內置的DI使用。

二、內置IOC

2.1 內置的IOC 有三種生命週期

  • Transient:瞬時生命週期, Transient服務在每次被請求時都會被創建一個新的對象。這種生命週期比較適用於輕量級的無狀態服務。
  • Scoped: Scoped生命週期的服務是每次web請求被創建,局部單例對象, 在某個局部內是同一個對象(作用域單例,本質是容器單例);一次請求內是一個單例對象,多次請求則多個不同的單例對象.
  • Singleton: Singleton生命能夠週期服務在第一被請求時創建,在後續的每個請求都會使用同一個實例。如果你的應用需要單例服務,推薦的做法是交給服務容器來負責單例的創建和生命週期管理,而不是自己來走這些事情。

我們先來看一張圖:

ASP.NET CORE 內置的IOC解讀及使用

ASP.NET Core本身已經集成了一個輕量級的 IOC容器,開發者只需要定義好接口後(抽象),並且對抽象的接口進行實現,再Startup.cs的ConfigureServices方法裡使用對應生命週期的注入,再調用的地方進行使用,比如構造函數注入等等。

在start.up類中ConfigureServices方法對實例進行註冊如下代碼:

<code> // This method gets called by the runtime. Use this method to add services to the container.     
public void ConfigureServices(IServiceCollection services)
{
\t\t\t\tConsole.WriteLine("ConfigureServices");
\t\t\t\tservices.AddControllersWithViews();
\t\t\t\t//注入生命週期為單例的服務
\t\t\t\t
\t\t\t\tservices.AddSingleton<isingletonservice>();
\t\t\t\t//注入生命週期為Scoped 的服務
\t\t\t\tservices.AddScoped<iscopedservice>();
\t\t\t\t//注入生命週期為瞬時的服務
\t\t\t\tservices.AddTransient<itransientservice>();
}/<itransientservice>/<iscopedservice>/<isingletonservice>/<code>

上面代碼我分別註冊了單例瞬時作用域的生命週期的服務。

下面簡單寫了一個例子讓大家看看這三個生命週期的實例的代碼

三個生命週期的抽象服務實現代碼如下:

<code>public class ScopedService : IScopedService
{
\t\t\tpublic string GetInfo()
\t\t\t{
\t\treturn $"this is scoped service ";
}
}

public class SingletonService : ISingletonService
{
\t\t\tpublic string GetInfo()
\t\t\t{
r\t\teturn $"this is singleton service"; }}public class TransientService : ITransientService{ public string GetInfo() { return $"this is transient service"; }}/<code>

控制器代碼如下:

<code>public IActionResult Index()       
{
\t\tusing (IServiceScope scope = HttpContext.RequestServices.CreateScope())
{
\t\tvar transientService = scope.ServiceProvider.GetService<itransientservice>();
\t\tvar transientService2 = scope.ServiceProvider.GetService<itransientservice>();

\t\tvar result = $"{transientService.GetInfo()} hashCode : {transientService.GetHashCode()}
";
\t\tresult += $"{transientService2.GetInfo()} hashCode : {transientService2.GetHashCode()}
";
\t\tViewBag.Transient = result;
\t
\t\tvar scopeService= scope.ServiceProvider.GetService<iscopedservice>();
\t\tvar scopeService2 = scope.ServiceProvider.GetService<iscopedservice>();
\t\tresult = $"{scopeService.GetInfo()} hashCode :{ scopeService.GetHashCode()}
";
\t\tresult += $"{scopeService2.GetInfo()} hashCode :{ scopeService2.GetHashCode()}
";
\t\tViewBag.Scope = result;

\t\tvar singletonService = scope.ServiceProvider.GetService<isingletonservice>();
\t\tvar singletonService2 = scope.ServiceProvider.GetService<isingletonservice>();
\t\tresult = $"{singletonService.GetInfo()} hashCode:{ singletonService.GetHashCode()}
";
\t\tresult += $"{singletonService2.GetInfo()} hashCode:{ singletonService2.GetHashCode()}
";
\t\tViewBag.Singletion = result;
\t\t\t}
\t\treturn View();
}/<isingletonservice>/<isingletonservice>/<iscopedservice>/<iscopedservice>/<itransientservice>/<itransientservice>/<code>

index.cshtml 視圖代碼如下:

<code>@{   
\t\t\tViewData["Title"] = "Home Page";
}
Transient生命週期
\t
@Html.Raw(ViewBag.Transient)

\tScoped生命週期
\t
@Html.Raw(ViewBag.Scope)

\tSingletion生命週期
\t
@Html.Raw(ViewBag.Singletion)
/<code>

分別運行兩次的結果如下圖:

ASP.NET CORE 內置的IOC解讀及使用

從上圖的運行的每個對象的hashCode 的結果看出 Transient生命週期是每次獲得對象都是一次新的對象; Scoped生命週期是在作用域是同一個對象,非作用域內則是新的對象; Singletion生命週期是最好理解的,是這個服務啟動後都是一個對象,也即是 全局單例對象。

2.2 注入的幾種方式

直接注入IServiceProvider的方式

services.AddSingleton();

然後在構造函數中通過如下方式獲取具體實現

<code>public HomeController(IServiceProvider serviceProvider)
{
\t\tvar singletonService = serviceProvider.GetService<singletonservice>();
}/<singletonservice>/<code>

通過GetServices方式

services.AddSingleton();

然後在構造函數中通過如下方式獲取具體實現

<code>public HomeController(IServiceProvider serviceProvider)
{
\t\tvar singletonService = serviceProvider.GetService<isingletonservice>();
}/<isingletonservice>/<code>

構造函數直接注入方式(推薦)

<code>public HomeController(ISingletonService singletonService)
{
\t\tvar _singletonService =singletonService;

}/<code>

集合方式注入

這種方式其實就是省去了注入 IServiceProvider的過程,直接將 GetServices獲取的結果進行注入。首先注入 interface及具體實現

<code>services.AddSingleton<isingletonservice>();
services.AddSingleton<isingletonservice>();/<isingletonservice>/<isingletonservice>/<code>

獲取的方式如下

<code>public HomeController(IEnumerable<isingletonservice> services)
{
\t\tvar singletoService1 = services.First();
\t\tvar singletoService2 = services.Skip(1).First();
}/<isingletonservice>/<code>

工廠方式注入

然後我們繼續注入Func這個工廠,這裡我們按 int來返回不同的實現,當然你也可以採用其他方式比如 string

<code>services.AddSingleton(provider =>{ 
\t\tFunc func = n =>
{
\t\tswitch (n)
{
case 1:
\t\t\t\t\treturn provider.GetService<singletonservice1>();
case 2:
return provider.GetService<singletonservice2>();
default:
throw new NotSupportedException();
\t\t }
\t\t };
\treturn func;
});/<singletonservice2>/<singletonservice1>
/<code>

然後在構造函數中通過如下方式獲取具體實現

<code>public HomeController(Func funcFactory)
{
\t\t\tvar singletonService1 = funcFactory(1);
\t\t\tvar singletonService2 = funcFactory(2);
}
/<code>

除了以上的幾個注入方式外,還可以通過反射的方式批量注入程序集的方式,這裡就不一一寫出具體的例子,自己去嘗試。

三、IOC怎麼解耦?

學習到這裡,大家對IOC和DI 的使用已經有了一定的掌握,上面我提到過 IOC的目標是 解耦、 屏蔽對象的實現細節這兩大優點;再來回顧上面的代碼實現 可以發現,推薦的注入方式是通過 抽象接口的方式進行注入而不是直接注入對象方式。

現在我列舉一個企業發展過程中很常見的一個例子,比如:我在一家企業擔任開發工作,開發了一個電商平臺系統,系統中需要用到日誌系統,由於當時的各種外在環境,我們使用的日誌是 nlog這個日誌組件;但是經過平臺的不斷髮展後,nlog 日誌組件已經不能滿足我們平臺的需求,需要尋求更智能的日誌系統,比如 Exceptionless,這時候我們就不得不權衡下現有代碼的可維護性。剛好這個電商平臺系統代碼使用了IOC 使得代碼可維護性比較強,日誌系統耦合性比較低,只需要簡單的幾行代碼即可實現日誌系統的大換血。現在來看下電商系統目前使用的日誌系統相關的代碼。

日誌組件服務註冊如下代碼:

<code>services.AddSingleton<ilogservice>();/<ilogservice>/<code>

各業務中使用nlog代碼大概如下:

<code>public HomeController(ILogService LogService) 
{
\t\t_logService =LogService;
\t\t_logService.Info("=========開始訪問========");
}/<code>

從上面的代碼中使用日誌的相關業務代碼都是通過IOC來進行控制反轉調用日誌服務,隱藏了日誌服務業務的實現細節;使用業務方無需關注日誌的實現細節,從而達到 了高度解耦的效果- 屏蔽對象實現細節。

現在我們進行日誌系統大換血代碼只需要實現一個新的日誌服務,我這裡創建 ExceptionlessLogService類繼承 ILogService即可,同時安排對應的人去實現 ExceptionlessLogService這個類就可以達到日誌系統升級的效果。

更換後的代碼如下:

<code>services.AddSingleton<ilogservice>();
改成services.AddSingleton<ilogservice>();/<ilogservice>/<ilogservice>/<code>

這樣就達到了一行代碼升級了整個系統的日誌系統,業務調用方無需任何的改動。


分享到:


相關文章: