Dependency Injection 初探筆記 (DI & IOC)

2021-06-16

初探依賴注入 Denpendency Injection Pattern(DI) 與控制反轉 Inversioin of Control (IOC) 設計模式。

logo

說明

什麼是依賴注入 💉
依賴注入是一種設計模式,目的是要減少程式碼的耦合程度,讓擴充與維護變得更容易。同時也因為 ASP.NET Core 將 DI 的觀念加入其框架之中,因此 DI 成為學習 ASP.NET Core 的基礎與背景知識。

而要使用依賴注入設計模式,重點在於結合使用 Interface,藉由將注入的物件介面化,相似於策略模式的使用方式,提升提供開發的擴充性,常見的注入方式包含:建構式注入、屬性注入、方法注入等。

依賴注入能夠帶來的好處 💗
將依賴的類別介面化,並如同策略模式一般替換演算法
使用裝飾模式,將依賴的類別可以層層包裝的方式加入功能,卻不影響使用的類別
藉由替換依賴類別的方式,可以產生專為測試情境需要的演算法,增加程式的可測試性

DI In ASP.NET Core

We make mvc support first, then we can use dependency injection in controller.

builder.Services.AddControllersWithViews();

var app = builder.Build();
app.MapDefaultControllerRoute();

app.Run();

We add a services folder and then add a service class.

public interface IMyservice
{
    Guid Id { get; }
}

public class MyService : IMyservice
{
    public Guid Id { get; } = Guid.NewGuid();

    // ⚠️ This will generate a new Guid every time
    // public Guid Id => Guid.NewGuid();
}

We using a controller to inject the service.

public class HomeController(IMyservice _service) : Controller
{
    public IActionResult Index()
    {
        ViewData["Id"] = _service.Id;
        return View();
    }
}

And also inject the service into the view.

@inject Mod05.Services.IMyservice Service

<div>Controller: @ViewData["Id"]</div>
<div>View: @Service.Id</div>

By the way, if we don't register the service, we will get an error like this picture.

We do register service in program.cs, and we can use AddTransient, AddScoped, AddSingleton to register service.

builder.Services.AddTransient<IMyservice, MyService>();
builder.Services.AddScoped<IMyservice, MyService>();
builder.Services.AddSingleton<IMyservice, MyService>();
Service Type Description
Transient A new instance is created every time it is requested (Id always different)
Scoped A new instance is created once per request (Id same in same requests)
Singleton A single instance is created for the lifetime of the application (Id always same)

If you are curious about when then service is disposed, you can use IDisposable interface.

public class MyService : IMyservice, IDisposable
{
    public Guid Id { get; } = Guid.NewGuid();

    public void Dispose()
    {
        Console.WriteLine($"{Id} disposed");
    }
}

At Transient and Scoped, the service will be disposed when the request is finished. And remebmer that Transient are created every time, so it will be disposed every time. And at Singleton, the service will be disposed when the application is stopped.

More

建構式注入, Constructor Injection

在寫 ASP.NET MVC 5 的時候,就會思考在 Service Layer 或者 Business Layer 也仿照 HomeController 去起始一個 DBContext 會不會造成資源的負擔,此外該 DBContext 在使用完畢後是否會自動釋放?

後來雖然研究的結果是 EntityFramework 具備處理的機制,不需要特別去覆寫 Dispose 也不會造成資料庫連線數的負擔,同時實際觀察 Database 的 Session 確實沒有多個 Session 來自 ASP.NET MVC 因此後來也沒有特別費心在此。

只是當時有思考過可以藉由 Controller 在 DBContext 傳送給 Business Layer 來使用,隱約也是有建構式注入的精神。

public interface IReadable
{
    void ReadData();
}

public class XMLReader : IReadable
{
    public void ReadData()
    {
        throw new NotImplementedException();
    }
}

public class CSVReader : IReadable
{
    public void ReadData()
    {
        throw new NotImplementedException();
    }
}

其中 Reader Class 是受其他類別所依賴的。

public class Reader
{
    private IReadable _reader;

    public Reader(IReadable reader)
    {
        _reader = reader;
    }

    public void Read() 
    {
        _reader.ReadData();
    }
}

由執行的類別負責決定其所依賴的物件。

static void Main()
{
    var reader = new Reader(new XMLReader());
    reader.Read();
}

DI Container

使用 DI Container 可以帶來下列好處:

  1. 自動註冊模型
  2. 自動解析物件的生成依賴
  3. 物件生命週期的管理

常見的 DI Container 包括 AutoFac、Ninject、Unity 等, ASP.NET Core 則是有原生的 DI Container 可以使用。

參考資料

ASP.Net Core MVC 進化之路 - Dependency Injection概念介紹

ASP.NET MVC 專案分層架構 Part.6 - DI/IoC 使用 Unity.MVC

相關連結

Pluralsight - Getting Started with Dependency Injection in .NET