読者です 読者をやめる 読者になる 読者になる

BEACHSIDE BLOG

MicrosoftとかC#を好むレンジャーの個人的メモ

ASP.NET5 MVC6 でDI(Dependency Injection)の設定

ASP.NET ASP.NET5 MVC6 C#

ASP.NET5 MVC6 で Depenency Injection(依存性の注入)をする際のメモです。

本家のドキュメントがあります。(あ....結構前に見たときから更新されてる......)
Dependency Injection in ASP.NET Core | Microsoft Docs

MSDNのBlogでは、2014年に出ているので、既に古いネタと言える内容ですね....
Dependency Injection in ASP.NET vNext | .NET Web Development and Tools Blog

> Environment

1. ASP.NET5 の Depenency Injection で設定できる種類

DIの設定は、Startup.csの中にあるConfigureServicesメソッドの中で設定します。
(といっても..たくさんDIするならメソッド化した方が良いとは思いますが)

DIを設定するための種類は、4種類あります。
ライフサイクルの長い順にざっくり説明すると....

種類 概要
Instance Web サーバー(って表現は正しい?)のインスタンス単位で作成される
Singleton インスタンスの単位は「Instance」と同じですが、lazy load
Scoped 1リクエストに対して、1インスタンス
Transient インスタンス生成時、常に新しいインスタンスが作られる

と(私は)理解しています。
「Instance」は、ConfigureServicesでインスタンス化して、シングルトン。
「Singleton」は、使われるタイミングでインスタンス化してシングルトン。
「Scoped 」と「Transient 」は上記通り、そして、今回の動作検証で最も確認したいとこ。

余談ですが、
インスタンス化...って、私はよく聞くし、だから私も言いますが、
英文でmaterializeって書いてあった本があったんですが、インスタンス化って言わない方がいいの?和製英語なの?とふと疑問..

では、作って動作見てみます。

2. Implimentation: ASP.NET5 MVC6プロジェクトの作成とDIするクラスの準備

Visual Studioでプロジェクトを作りますが、ここら辺の作業は省略します。

で、DIするクラスを作るところから書きます。
今回のデモでは、前述4種類に対応する**Serviceクラスを作ってDIします。
ということで、まずインターフェースを4つ用意。

using System;

namespace MVC6DiDemo.Services.Abstractions
{
    public  interface IScopedService:IService
    {
    }

    public interface ISingletonService : IService
    {
    }


    public interface ITransientService : IService
    {
    }


    public interface IInstanceService : IService
    {
    }

    public interface IService
    {
        DateTime CreateDateTime { get; }
        int CtorCount { get; }
    }
}

デモなので、4つのインターフェースを1つのファイルに書いて手抜きってます。で同じコード書くのがつらいので、「IService」を継承。

次に、実体のServiceクラスたち。DIの4種類にあわせて4つのクラスを用意します。クラス名と、実装してるインターフェースが違うだけで中身は一緒です。
やっていることは、コンストラクターが呼ばれた時間と、その回数を見れるようにしています。
(ここは、諸事情により継承してない)

InstanceService クラス

using System;
using MVC6DiDemo.Services.Abstractions;

namespace MVC6DiDemo.Services
{
    public class InstanceService : IInstanceService
    {
        public InstanceService()
        {
            _createDateTime = System.DateTime.Now;
            _count++;
        }
        private static DateTime _createDateTime;
        public DateTime CreateDateTime => _createDateTime;

        private static int _count;
        public int CtorCount => _count;
    }
}

SingletonService クラス

using System;
using MVC6DiDemo.Services.Abstractions;

namespace MVC6DiDemo.Services
{
    public class SingletonService : ISingletonService
    {
        public SingletonService()
        {
            _createDateTime = System.DateTime.Now;
            _count++;
        }
        private static DateTime _createDateTime;
        public DateTime CreateDateTime => _createDateTime;

        private static int _count;
        public int CtorCount => _count;
    }
}

ScopedService クラス

using System;
using MVC6DiDemo.Services.Abstractions;

namespace MVC6DiDemo.Services
{
    public class ScopedService : IScopedService
    {
        public ScopedService()
        {
            _createDateTime = System.DateTime.Now;
            _count++;
        }
        private static DateTime _createDateTime;
        public DateTime CreateDateTime => _createDateTime;
        private static int _count;
        public int CtorCount => _count;
    }
}

TransientServiceクラス

using System;
using MVC6DiDemo.Services.Abstractions;

namespace MVC6DiDemo.Services
{
    public class TransientService: ITransientService
    {
        private static int _count;
        private static DateTime _createDateTime;
        public TransientService()
        {
            _createDateTime = System.DateTime.Now;
            _count++;
        }
        public DateTime CreateDateTime => _createDateTime;
        public int CtorCount => _count;
    }
}

3. Implimentation: DIの設定

プロジェクト直下にある「Startup.cs」のStartupクラスのConfigureServicesメソッドの下の方に以下のコードを追加します。

public void ConfigureServices(IServiceCollection services)
{
    // ..デフォルトで書いてあるコードは省略...

    services.AddInstance<IInstanceService>(new InstanceService());
    services.AddSingleton<ISingletonService, SingletonService>();
    services.AddScoped<IScopedService, ScopedService>();
    services.AddTransient<ITransientService, TransientService>();
}

IServiceCollectionの「AddInstance」「AddSingleton」「AddScoped」「AddTransient」で設定します。
「Instance」で設定する場合のみ、実装方法が異なります。ここでがっつりインスタンス化してます。

4. Implimentation: コントローラー

デフォルトで用意されているHomeControllerと使ってしまいます。

まずは、メンバー変数とコンストラクターを追加します。
「Scoped」と「Transient」は、1リクエストで複数インスタンスある場合に動きが異なるようなので、2づつ用意しています。

public class HomeController : Controller
{
    private readonly IInstanceService _instanceService;
    private readonly ISingletonService _singletonService;

    private readonly IScopedService _scopedService1;
    private readonly IScopedService _scopedService2;

    private readonly ITransientService _transientService1;
    private readonly ITransientService _transientService2;

    public HomeController(IInstanceService instanceService, ISingletonService singletonService, ITransientService tran1, ITransientService tran2,IScopedService scope1,IScopedService scope2)
    {
        _instanceService =  instanceService;
        _singletonService = singletonService;
        _transientService1 = tran1;
        _transientService2 = tran2;
        _scopedService1 = scope1;
        _scopedService2 = scope2;
    }

DI対象の**Serviceクラスがたくさんあるので、コンストラクター見苦しい...。

クライアントサイドから呼ばれる「Index」メソッドは、以下のように、
各Serviceクラスのコンストラクター呼ばれた時間と回数をViewBagに入れて、Viewで取得できるようにしてます。

private static string Stamp(IService service) => $"time: {service.CreateDateTime}; count:{service.CtorCount};";

public IActionResult Index()
{
    ViewBag.CurrentDate = DateTime.Now;

    ViewBag.InstanceTime = Stamp(_instanceService);
    ViewBag.SingletonTime = Stamp(_singletonService);

    ViewBag.TransientTime1 = Stamp(_transientService1);
    ViewBag.TransientTime2 = Stamp(_transientService2);

    ViewBag.ScopedTime1 = Stamp(_scopedService1);
    ViewBag.ScopedTime2 = Stamp(_scopedService2);

    return View();
}

5. Implimentation: ビュー

コントローラに合わせて、Viewは、HomeのIndexビューを変更しましょう。
デフォルトで作られるViewのカルーセル麻紀の下あたりに、VeiwBagの値を呼び出してみます。

<h2>
    current time:   @ViewBag.CurrentDate
</h2>
<div class="row">
    <div class="col-md-3">
        <h2>Instance</h2>
        <p>
            @ViewBag.InstanceTime
        </p>
    </div>
    <div class="col-md-3">
        <h2>Singleton </h2>

        <p>
            @ViewBag.SingletonTime
        </p>

    </div>
    <div class="col-md-3">
        <h2>Scoped </h2>
        <p>
            ScopedTime1: @ViewBag.ScopedTime1
        </p>
        <p>
            ScopedTime2: @ViewBag.ScopedTime2
        </p>
    </div>
    <div class="col-md-3">
        <h2>Transient </h2>
        <p>
            TransientTime1: @ViewBag.TransientTime1
        </p>
        <p>
            TransientTime2: @ViewBag.TransientTime2
        </p>

    </div>
</div>

6. 動作検証

あ、Startupのコンストラクターで、起動時の時刻をコマンドに表示するコードも書きました...
デバッグは、今回は「Web」でします。
f:id:beachside:20160115153437p:plain


デバッグを開始した時間がコンソールに表示されます。
f:id:beachside:20160115153557p:plain


では、「http://localhost:5000/」(URLは人によって異なる場合あり)で、Viewを表示

f:id:beachside:20160115153734p:plain

インスタンスを起動したタイミングとほぼ同じ時間で、「Instance」を指定しているクラスは生成されています。
「Singleton」を指定したクラスは、呼び出した時間と同時刻くらいに生成されているのがわかります。

また、「Scoped」のクラスは、1リクエストに対してシングルトンなので、コントローラーに2インスタンス持っていますが、コンストラクターの呼び出し回数は1回ですね。
それに対して「Transient」は、コントローラーに2インスタンスあるので、2回コンストラクターが呼び出されています。

リロードを2回してみると....
f:id:beachside:20160115154352p:plain

「Instance」と「Singleton」は、変わらず。
「Scoped」は、合計3回コールしているので、インスタンスの呼び出し回数が3回、「Transient」は、6回で、時間も更新されていますね。


ということで、想像通りの動きで良かったです。