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(依賴注入)?

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

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生命能夠周期服務在第一被請求時創建,在后續的每個請求都會使用同一個實例。如果你的應用需要單例服務,推薦的做法是交給服務容器來負責單例的創建和生命周期管理,而不是自己來走這些事情。

我們先來看一張圖:
使用圖.png

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

startup類中ConfigureServices方法對實例進行注冊如下代碼:

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine("ConfigureServices");
            services.AddControllersWithViews();

            //注入生命周期為單例的服務
            services.AddSingleton<ISingletonService, SingletonService>();
            //注入生命周期為Scoped 的服務
            services.AddScoped<IScopedService, ScopedService>();
            //注入生命周期為瞬時的服務
            services.AddTransient<ITransientService, TransientService>();
        }

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

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

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

public class ScopedService : IScopedService
{
        public string GetInfo()
        {
            return $"this is scoped service ";
        }
}

public class SingletonService : ISingletonService
{
        public string GetInfo()
        {
            return $"this is singleton service";
        }
}

public class TransientService : ITransientService
{
        public string GetInfo()
        {
            return $"this is transient service";
        }
}

控制器代碼如下:

public IActionResult Index()
        {
            using (IServiceScope scope = HttpContext.RequestServices.CreateScope())
            {
                var transientService = scope.ServiceProvider.GetService<ITransientService>();
                var transientService2 = scope.ServiceProvider.GetService<ITransientService>();

                var result = $"{transientService.GetInfo()} hashCode : {transientService.GetHashCode()} <br/>";
                result += $"{transientService2.GetInfo()} hashCode : {transientService2.GetHashCode()} <br/>";

                ViewBag.Transient = result;

                var scopeService= scope.ServiceProvider.GetService<IScopedService>();
                var scopeService2 = scope.ServiceProvider.GetService<IScopedService>();

                result = $"{scopeService.GetInfo()} hashCode :{ scopeService.GetHashCode()} <br/>";
                result += $"{scopeService2.GetInfo()} hashCode :{ scopeService2.GetHashCode()} <br/>";
                ViewBag.Scope = result;

                var singletonService = scope.ServiceProvider.GetService<ISingletonService>();
                var singletonService2 = scope.ServiceProvider.GetService<ISingletonService>();
                result = $"{singletonService.GetInfo()} hashCode:{ singletonService.GetHashCode()} <br/>";
                result += $"{singletonService2.GetInfo()} hashCode:{ singletonService2.GetHashCode()} <br/>";
                ViewBag.Singletion = result;
            }
            return View();
        }

index.cshtml 視圖代碼如下:

@{
    ViewData["Title"] = "Home Page";
}

<b>Transient生命周期</b>
<div>
    @Html.Raw(ViewBag.Transient)
</div>

<b>Scoped生命周期</b>
<div>
    @Html.Raw(ViewBag.Scope)
</div>

<b>Singletion生命周期</b>
<div>
    @Html.Raw(ViewBag.Singletion)
</div>

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

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

2.2 注入的幾種方式

直接注入IServiceProvider的方式

services.AddSingleton ();

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

public HomeController(IServiceProvider serviceProvider)
{
       var singletonService = serviceProvider.GetService<SingletonService>();
}

通過GetServices方式

services.AddSingleton<ISingletonService, SingletonService>();

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

public HomeController(IServiceProvider serviceProvider)
 {
      var singletonService = serviceProvider.GetService<ISingletonService>();
 }

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

public HomeController(ISingletonService singletonService)
 {
     var _singletonService =singletonService;
 }

集合方式注入

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

services.AddSingleton<ISingletonService, SingletonService1>();
services.AddSingleton<ISingletonService, SingletonService2>();

獲取的方式如下

public HomeController(IEnumerable<ISingletonService> services)
{
    var singletoService1 = services.First();
    var singletoService2 = services.Skip(1).First();
}

工廠方式注入

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

services.AddSingleton(provider =>
{
    Func<int, ISingletonService> func = n =>
    {
        switch (n)
        {
            case 1:
                return provider.GetService<SingletonService1>();
            case 2:
                return provider.GetService<SingletonService2>();
            default:
                throw new NotSupportedException();
        }
    };
    return func;
});

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

public HomeController(Func<int, ISingletonService> funcFactory)
{
    var singletonService1 = funcFactory(1);
    var singletonService2 = funcFactory(2);
}

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

三、IOC怎么解耦?

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

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

日志組件服務注冊如下代碼:

services.AddSingleton<ILogService, nLogService>();

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

public HomeController(ILogService LogService)
 {
     _logService =LogService;
     _logService.Info("=========開始訪問========");
 }

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

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

更換后的代碼如下:

services.AddSingleton<ILogService, NLogService>();
改成
services.AddSingleton<ILogService, ExceptionlessLogService>();

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM