iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
1
Modern Web

.Net Core 網站開發 101系列 第 7

Dependency Injection 依賴注入

依賴注入這個主題大概就可以寫五天了吧(?),這個系列就不多作介紹了,詳細的可以參考英文微基,網路上也有很多相關文章 (其實是自己講不清楚)。今天會針對如何在 ASP.NET Core 中使用依賴注入作介紹。

服務的生命周期

開始註冊服務之前,我們先來了解服務的生命周期。註冊服務時需設定生命周期,有三種生命周期設定:

  • Transient:每次被注入時都會建立一個新的實例,適合輕量級、無狀態的服務。
  • Scoped:應用程式每次收到請求時都會建立一個新的實例。
  • Singleton:在應用程式中第一次被注入時會建立一個實例;或在 Startup.ConfigureSerivces 中註冊時提供新的實例。之後每次被注入時都會使用同一個實例。如果應用程式中需要 Singleton 行為,建議讓服務容器來管理其生命周期,不要自己實作。

生命周期範例

看這麼多字,不如用範例來比較一下不同生命周期的差異吧!我們以唯一識別碼「OperationId」來代表不同的工作,並觀察使用不同生命周期註冊服務時的差異。參考完整原始碼

首先建立不同介面代表不同生命周期設定的類型:

public interface IOperation
{
    Guid OperationId { get; }
}

public interface IOperationTransient : IOperation
{
}

public interface IOperationScoped : IOperation
{
}

public interface IOperationSingleton : IOperation
{
}

public interface IOperationSingletonInstance : IOperation
{
}

建立 Operation 類別來實作這些介面。建構元會傳入一個 GUID,如果沒有傳入則會建立一個新的 GUID:

public class Operation :
        IOperationTransient,
        IOperationScoped,
        IOperationSingleton,
        IOperationSingletonInstance
{
    public Operation() : this(Guid.NewGuid())
    {
    }

    public Operation(Guid id)
    {
        OperationId = id;
    }

    public Guid OperationId { get; }
}

新增一個相依四種 Operation 類型的 OperationService 類別:

public class OperationService
{
    public OperationService(
        IOperationTransient transientOperation,
        IOperationScoped scopedOperation,
        IOperationSingleton singletonOperation,
        IOperationSingletonInstance instanceOperation)
    {
        TransientOperation = transientOperation;
        ScopedOperation = scopedOperation;
        SingletonOperation = singletonOperation;
        SingletonInstanceOperation = instanceOperation;
    }

    public IOperationTransient TransientOperation { get; }
    public IOperationScoped ScopedOperation { get; }
    public IOperationSingleton SingletonOperation { get; }
    public IOperationSingletonInstance SingletonInstanceOperation { get; }
}

Startup.ConfigureServices 方法中,以不同的生命周期設定,將服務新增到服務容器中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));

    services.AddTransient<OperationService, OperationService>();
}

新增一個 DependencyInjectionLifetimesController 控制器類別,注入上面建立的這些服務,並新增一個 Index Action,把被注入的服務都加到 ViewBag 中以在 View 中使用。

public class DependencyInjectionLifetimesController : Controller
{
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationScoped _scopedOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationSingletonInstance _singletonInstanceOperation;
    private readonly OperationService _operationService;

    public DependencyInjectionLifetimesController(IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation, OperationService operationService)
    {
        _transientOperation = transientOperation;
        _scopedOperation = scopedOperation;
        _singletonOperation = singletonOperation;
        _singletonInstanceOperation = singletonInstanceOperation;
        _operationService = operationService;
    }

    // GET: /<controller>/
    public IActionResult Index()
    {
        ViewBag.Transient = _transientOperation;
        ViewBag.Scoped = _scopedOperation;
        ViewBag.Singleton = _singletonOperation;
        ViewBag.SingletonInstance = _singletonInstanceOperation;
        ViewBag.Service = _operationService;

        return View();
    }
}

最後,建立 DependencyInjectionLifetimes/Index.cshtml,對應到 Controller/Action 的 View

@{
    ViewData["Title"] = "Dependency Injection Lifetimes Example";
}

<style>
    table tr td {
        padding: 5px;
    }
    table tr td:first-child {
        text-align: right;
        font-weight: bold;
    }
</style>

<h2>生命周期範例</h2>
<hr />

<h3>Controller Operations</h3>
<table>
    <tr>
        <td>Transient</td>
        <td>@ViewBag.Transient.OperationId</td>
    </tr>
    <tr>
        <td>Scoped</td>
        <td>@ViewBag.Scoped.OperationId</td>
    </tr>
    <tr>
        <td>Singleton</td>
        <td>@ViewBag.Singleton.OperationId</td>
    </tr>
    <tr>
        <td>SingletonInstance</td>
        <td>@ViewBag.SingletonInstance.OperationId</td>
    </tr>
</table>

<hr />

<h3>OperationService Operations</h3>
<table>
    <tr>
        <td>Transient</td>
        <td>@ViewBag.Service.TransientOperation.OperationId</td>
    </tr>
    <tr>
        <td>Scoped</td>
        <td>@ViewBag.Service.ScopedOperation.OperationId</td>
    </tr>
    <tr>
        <td>Singleton</td>
        <td>@ViewBag.Service.SingletonOperation.OperationId</td>
    </tr>
    <tr>
        <td>SingletonInstance</td>
        <td>@ViewBag.Service.SingletonInstanceOperation.OperationId</td>
    </tr>
</table>

執行應用程式後,用瀏覽器開兩個分頁觀察 OperationId 在兩次請求的差異:
https://ithelp.ithome.com.tw/upload/images/20181021/20107875NVLuyCA6ul.png

  • Transient:在 Controller 或 Service 中被注入時,都會產生一個新的實例。
  • Scoped:同一個請求時會相同;不同請求則否。
  • Singleton:都會是同一個實例。

參考資料


上一篇
Startup 啟動類別
下一篇
Middleware 中介層 - 1/2
系列文
.Net Core 網站開發 10131

尚未有邦友留言

立即登入留言