iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
0
Modern Web

我與 ASP.NET Core 的 30天系列 第 4

[Day04] 依賴注入 (Dependency Injection) - 我與 ASP.NET Core 3 的 30天

開始前的謎之聲:本來想要每天標題都打得很像愛情故事的,殊不知第三天開始就沒梗了,之後的標題都會以正經的方式呈現。

正文開始

ASP.NET Core 大量使用依賴注入(DI),落實了在類別的相依性之間達成控制反轉(IoC)的技術。

先簡單介紹一下 IoC/DI

IoC 控制反轉 (Inversion of Control)

IoC是一個物件導向的設計原則、一種概念,主要作用就是降低類別間的依賴

原本A程式中需要使用C類別 就必須在A中new 一個C的實體
B程式也得用到C類別 就又得多 new 一個C的實體
https://ithelp.ithome.com.tw/upload/images/20200918/20129389EaLgDg3zmB.png
上圖為例 A跟B對於三個類別都是直接性的依賴,為了避免耦合性過高,我們就得降低A跟B對於各類別的依賴。
IoC 就是A與B 對類別的控制權轉移給第三方
將對於類別的依賴,轉移到依賴第三方的容器

DI 依賴注入(Dependency Injection)

DI 是實踐 IoC 的一種設計模式

透過把程式中依賴的實體,注入到被動接收的物件中

https://ithelp.ithome.com.tw/upload/images/20200918/20129389hSsGiO8V5J.png

舉個例子:

今晚我想來點胖老爹的美式炸雞桶,以往都是打電話請該店外送,結果今天居然跟我說太忙了人手不足!

上述可知我直接耦合店家的外送員

如果今天變成,我透過Uber Eats 來點餐,我就不需要管店家是否有外送員,而是由Uber Eats幫我安排外送員。

範例可知,我(被動接收的物件)把依賴的對象從店家外送員(被依賴的物件)變成依賴外送平台(DI Container)

ASP.NET Core DI

private readonly SampleService _service;

public SampleController()
{
    _service = new SampleService();
}

上方範例是一般使用實體的方式,都是new一個實體

在 ASP.NET Core 中 我們可以透過 Startup.cs 中的 ConfigureServices() 來註冊應用程式中所需要的服務進DI容器

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<SampleService, SampleService>();
    services.AddControllers();
}

上方範例所示,service就是我們的DI容器,透過介面定義所需要的類別,在之後需要做抽換的時候,只要實作同一個介面的類別,就可以進行替換了。

private readonly IService _service;

public SampleController(IService service)
{
    _service = service;
}

註冊完之後便可以在建構式中注入所需要的服務。

備註:在 ASP.NET Core 已經有內建許多可注入的服務

在DI 容器中控管服務的生命週期

Transient (暫時性的實體)
-每次注入時都會建立新的物件

Scoped (具範圍的實體)
-每個 HTTP Request只會共用一個物件,並在第一次注入時建立物件

Singleton (單一實體)
-當應用程式啟動時,會在第一次注入時或在註冊進 DI 容器時建立物件

接著做個範例,來實際看三種生命週期的差異。

首先我們先建立以下檔案
https://ithelp.ithome.com.tw/upload/images/20200918/20129389IqsFptCui4.png
加入內容如下
ISample.csIScoped.csISingleton.csITransient.cs

public interface ISample
{
}

public interface IScoped : ISample
{
}

public interface ISingleton : ISample
{
}

public interface ITransient : ISample
{
}

SampleService.cs

public class SampleService
{
    private readonly ISample _transient;
    private readonly ISample _scoped;
    private readonly ISample _singleton;
    public SampleService(ITransient transient,
                            IScoped scoped,
                            ISingleton singleton)
    {
        _transient = transient;
        _scoped = scoped;
        _singleton = singleton;
    }

    public string GetSampleHashCode()
    {
        return $"Transient: {_transient.GetHashCode()}, "
                + $"Scoped: {_scoped.GetHashCode()}, "
                + $"Singleton: {_singleton.GetHashCode()}";
    }
}

接著在StartupConfugureServices() 中註冊各種生命週期的物件

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IScoped, SampleClass>();
    services.AddSingleton<ISingleton, SampleClass>();
    services.AddTransient<ITransient, SampleClass>();
    services.AddTransient<SampleService, SampleService>();
    services.AddControllers();
}

並在Controller的建構式注入服務的實體

public class SampleController : ControllerBase
{
    private readonly ISample _transient;
    private readonly ISample _scoped;
    private readonly ISample _singleton;
    private readonly SampleService _service;
    public SampleController(ITransient transient,
                            IScoped scoped,
                            ISingleton singleton,
                            SampleService service)
    {
        _transient = transient;
        _scoped = scoped;
        _singleton = singleton;
        _service = service;
    }

接著我們新增一個端點,來取得每個物件的HashCode

[HttpGet("")]
public ActionResult<IDictionary<string, string>> Get()
{
    var serviceHashCode = _service.GetSampleHashCode();
    var controllerHashCode = $"Transient: {_transient.GetHashCode()}, "
                            +$"Scoped: {_scoped.GetHashCode()}, " 
                            +$"Singleton: {_singleton.GetHashCode()}";
    return new Dictionary<string, string> {
        { "Service", serviceHashCode},
        { "Controller", controllerHashCode}
     };
}

完成後就可以輸入 dotnet run 運行應用程式,然後透過瀏覽器來觀看結果

可以看到,兩次Request的結果:
https://ithelp.ithome.com.tw/upload/images/20200918/20129389shBYRmSJgr.png

https://ithelp.ithome.com.tw/upload/images/20200918/20129389OECPYgjAxl.png

Transient 在每次注入的時候都是全新的實體,所以每次取得的HashCode 都會不一樣

Scoped 在一次的Request中都會注入同一個實體,下一個Request則會注入新的實體,所以在同一個Request的HashCode會一致,但是下個Request的HashCode就會不一樣

Singleton的HashCode都是一致的,證明運行期間Singleton的物件都會使用同一個

ASP.NET Core中就自帶DI,所以建議任何服務本身最好也用DI注入其他服務,也不要在服務類別中直接new出其他服務的物件。

參考連結
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1


上一篇
[Day03] Middleware- 我與 ASP.NET Core 3 的 30天
下一篇
[Day05] Entity Framework Core與DB First - 我與 ASP.NET Core 3 的 30天
系列文
我與 ASP.NET Core 的 30天31

1 則留言

0
congimei
iT邦新手 5 級 ‧ 2021-04-27 10:09:48

請問 SampleClass.cs 內容能不能貼上來,不太懂
services.AddScoped<IScoped, SampleClass>();
services.AddSingleton<ISingleton, SampleClass>();
services.AddTransient<ITransient, SampleClass>();
這裡每個都是用 SampleClass,不知道與那三個Interface有何關連。

ATai iT邦新手 5 級 ‧ 2021-04-29 17:06:19 檢舉

您好,SampleClass的部分我主要只有建立一個空類別,所以沒有分享內容。
會分三個主要是因為,使用同一個類別,要區分三種不同生命週期做注入,來展示差異。
每個Interface之間沒有關聯,只是要區隔注入項目的內容或週期

不知道這樣有沒有解答到你的疑惑

我要留言

立即登入留言