ASP-NET Core 依賴注入初識與思考

一、前言

上篇實例中,我們通過日誌的方式舉例說明,其中通過代碼創建了一個ILogger的接口,並實現接口實例,基於控制反轉的模式,依賴的創建也移交到了外部,但是也發現存在了問題,如果類似存在這樣多個接口和實現類,依賴太多,一一創建,沒有統一的管理,這反而增加了實際工作量麻煩。

因此我們需要一個可以統一管理系統中所有的依賴的地方,因此,IoC 容器誕生了。

容器負責兩件事情:

所以在這一篇中,我們主要講述 Asp.Net Core 中內置的 IoC 容器。

二、說明

在 Asp.Net Core 中已經爲我們集成提供了一個內置的 IoC 容器,我們可以看到在Startup.csConfigureServices中,涉及到依賴注入的核心組件, 一個**「負責實例註冊」**的IServiceCollection和一個**「負責提供實例」**的IServiceProvider

簡單的說就是兩步:1. 把實例註冊到容器中;2. 從容器中獲取實例

而在這其中,IServiceCollection爲實現將開發者定義好的實例註冊進去提供了**「三種方法」**。

分別是:

「AddTransient」  、「AddScoped」「AddSingleton」

而這三種不同實例方法也對應的着三種不同的實例**「生命週期」**。

「三種不同的生命週期如下:」

2.1 暫時性

「AddTransient」

每次在向服務容器進行請求時都會創建新的實例,這種生存期適合輕量級、 無狀態的服務。

2.2 作用域內

「AddScoped」

在每次 Web 請求時被創建一次實例,生命週期橫貫整次請求。

局部單例對象, 在某個局部內是同一個對象 (作用域單例, 本質是容器單例); 一次請求內是一個單例對象,多次請求則多個不同的單例對象。

2.3 單例

「AddSingleton」

創建單例生命週期服務的情況如下:

其後的每一個後續請求都使用同一個實例。如果開發者的應用需要單例服務情景,推薦的做法是交給服務容器來負責單例的創建和生命週期管理,而不是手動實現單例模式然後由開發者在自定義類中進行操作。

不要從單一實例解析指定了作用域的服務。當處理後續請求時,它可能會導致服務處於不正確的狀態。可以從範圍內或暫時性服務解析單一實例服務。

三、開始

3.1 接口

定義三個接口,分別測試 Singleton,Scope,Transient 三種,一個 TestService 服務

    public interface ITransientService
    {
           string GetGuid();
    }
    public interface IScopedService
    {
        string GetGuid();
    }
    public interface ISingletonService
    {
        string GetGuid();
    }
    public interface ITestService  
    {
        public  string GetSingletonID();
        public string GetTransientID();
        public string GetScopedID();
    }

3.2 實現

根據上面定義的幾種接口,並一一實現對應的接口

    public class TransientService : ITransientService
    {
        public string OperationId { get; }
        public TransientService()
        {
            OperationId = Guid.NewGuid().ToString()[^4..];
        }
        public string GetGuid()
        {
            return $"這是一個 Transient service : " + OperationId;
        }
    }
    public class ScopedService : IScopedService
    {
        public string OperationId { get; }
        public ScopedService()
        {
            OperationId = Guid.NewGuid().ToString()[^4..];
        }

        public string GetGuid()
        {
            return $"這是一個 scoped service : "+ OperationId;  
        }
    }
    public class SingletonService : ISingletonService
    {
        public string OperationId { get; }
        public SingletonService()
        {
            OperationId = Guid.NewGuid().ToString()[^4..];
        }


        public string GetGuid()
        {
            return $"這是一個 Singleton service : " + OperationId;
        }
    }
    public class TestService : ITestService
    {
        private ITransientService _transientService;
        private IScopedService _scopedService;
        private ISingletonService _singletonService;
        public TestService(ITransientService transientService, IScopedService scopedService, ISingletonService singletonService)
        {
            _transientService = transientService;
            _scopedService = scopedService;
            _singletonService = singletonService;
        }

        public string GetSingletonID()
        {
            return _singletonService.GetGuid();
        }
        public string GetTransientID()
        {
            return _transientService.GetGuid();
        }
        public string GetScopedID()
        {
            return _scopedService.GetGuid();
        }
    }

3.3 注入

Startup.cs類文件ConfigureServices方法中,注入依賴

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddTransient<ITransientService, TransientService>();
            services.AddSingleton<ISingletonService, SingletonService>();
            services.AddScoped<IScopedService, ScopedService>();
            services.AddScoped<ITestService, TestService>();
        }

3.4 調用

定義一個控制器,實現調用

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private ITransientService _transientService;
    private IScopedService _scopedService;
    private ISingletonService _singletonService; 
    private ITestService _testService;
    public TestController(ITransientService transientService, IScopedService scopedService, ISingletonService singletonService, 
       ITestService  testService
        )
    {
        _transientService = transientService;
        _scopedService = scopedService;
        _singletonService = singletonService;
         
        _testService = testService;
    }

    [HttpGet]
    public JsonResult Get()
    {
        var data1 =   _transientService.GetGuid();
        var data2 = _testService.GetTransientID();

        var data3 = _scopedService.GetGuid();
        var data4 = _testService.GetScopedID();

        var data5 = _singletonService.GetGuid();
        var data6 = _testService.GetSingletonID();
        return new JsonResult(new { 
            data1, 
            data2, 
            data3 ,

            data4,
            data5,
            data6,
        });
    }
}

在上面中我們瞭解到,注入的方式一般有三種, 構造函數注入, 方法注入, 屬性注入,而在 ASP.NET Core 中自帶的這個 IoC 容器, 默認採用了構造函數注入的方式。

3.5 測試

啓動運行項目,訪問接口 / Test

效果如下:

3.6 對比

對比兩次的請求訪問可以發現,上面我們一共得到了 4 個 Transient 實例,2 個 Scope 實例,1 個 Singleton 實例。

「在請求中,AddSingleton 方式的 id 值相同;」

「AddScope 方式兩次請求之間不同,但同一請求內是相同的;」

「AddTransient 方式在同一請求內的多次注入間都不相同。」

3.7 小結

通過上述的代碼示例,也證實了之前的三種方式對應不同生命週期的說明。

暫時性 (Transient) : 生命週期是每次獲得對象都是一次新的對象, 每一次都不一樣。

作用域內 (Scoped) : 生命週期是在每一次請求作用域內是同一個對象,非作用域內則是新的對象。

單例(Singletion) : 生命週期是這個服務啓動後都是一個對象,也即是全局單例對象

四、其他 Ioc 容器

通過上述的瞭解,我們知道 IoC 容器是一個依賴注入框架,在. NET Core 中也提供了內置的 IoC 容器,通過 AddXXX 方法來實例依賴對象,而在實際開發中,就是每一個實例都需要一個個的添加,這樣的實現方式在小項目中還好,但是如果在複雜大型的項目中,就略向麻煩些,可能需要添加很多方法來實現,整體的可觀性也不好。

爲了達到可以簡化我們工作量,應該採用批量註冊,因此我們也可以引入其他的 Ioc 容器框架,實現更多的功能和擴展。

在平時開發中,常用的 IoC 框架有很多,而在這裏我們選擇用 Autofac,這也是在. net 下比較流行的,其他的框架不做說明,可自行查閱瞭解。

「ASP.Net Core 中使用 Autofac 框架注入 (在後續篇章會具體說明)」

五、思考

在實際的開發中,對於這幾種生命週期,我們應該如何應用呢?

比如,在像 DBContext 這種實例,在實際開發中是用 Transient 還是 Scoped 呢?

「建議使用 Scoped」

因爲有些對象在請求中可以需要用到多個方法或者多個 Service、Repository 的時候,爲了減少實例初始化的消耗,實現事務功能,可以在整個請求的生命週期共用一個 Scope。

其他的思考:

  1. ASP.NET Core 中,默認採用了構造函數注入的方式,如果採用屬性注入或者方法注入,又該怎麼實現?

  2. 一個接口多種實現的時候,我們將多種實現都給注入進了依賴注入容器中,但是在服務調用的時候總是獲取到最後注入的那個方法的實現,這時候就在想能不能實現動態的選擇使用哪種實現呢?

  3. 一個作用域(Scoped)服務中注入一個瞬時(Transient)服務時,瞬時服務中的值還會每次都變化嗎?

  4. 鏈式注入時,生存期的選擇,三種注入方式的權重問題

以上的思考,大家可以說說自己的想法。

「在後續的篇章中,也會對這些問題,進行深入討論說明。」

六、總結

本篇主要介紹了什麼是 IoC 容器,瞭解到它是 DI 構造函注入的框架,它管理着依賴項的生命週期以及映射關係,同時也介紹實踐了在 ASP.Net Core 中, 默認提供的內置 IoC 容器,以及它的實例註冊方式和相應的生命週期。

如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論, 不斷學習, 共同進步。🤣

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/q8GU7mEUljFods0xQrRlzA