nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

【連載】ASP.NET Web API を使おう:第4回 Simple Injector(DIコンテナ)を適用する

本エントリーは連載「ASP.NET Web APIを使おう」の第3回となります。連載の目次はこちら。

www.nuits.jp

これまでWeb APIを作成し、swagger-codegenを使って生成したクライアントから、作成したAPIを呼び出す方法について説明してきました。

今回はWeb APIつまりサーバーサイドにDependency Injection(DI)コンテナを適用する方法を解説します。DIコンテナーには Simple Injector を今回は選択しました。

DIコンテナは星の数ほどありますが、なぜ Simple Injector を選択したかは、以下の記事をご覧ください。

www.nuits.jp

こちらに記載している内容に加え、次のような観点から Simple Injector を取り上げました。

  • 全体的にモダンなアーキテクチャが採用されているように思えること
  • コミュニティが十分に成熟しているように見えること
  • 結果、ドキュメントなども十分に揃っていること(英語ですが) 

なお本エントリーでは、DI そのものの解説や、Simple Injector の詳細な解説は省きます。

1. 前提条件

本連載で作成してきたコードが存在する前提で説明を進めます。また以下の環境の元記載しています。

  • Visual Studio 2017 Version 15.5.6
  • .NET Framework 4.7.1

2. 事前準備

Simple Injector を適用する前に、Simple Injector によって注入する対象を先に用意しておきます。

HelloWebApi プロジェクトの Models フォルダの下に次の二つを追加してください。

  1. IEmployeeRepository インターフェース
  2. EmployeeRepository クラス

f:id:nuitsjp:20180215104921p:plain

その上で、つぎのように実装しましょう。

public interface IEmployeeRepository
{
    List<Employee> GetEmoloyees();
}

public class EmployeeRepository : IEmployeeRepository
{
    public List<Employee> GetEmoloyees()
    {
        return new List<Employee>
        {
            new Employee{Id = 1, Name = "山田 太郎", BirthDay = DateTime.Parse("1970/01/01")},
            new Employee{Id = 2, Name = "佐藤 花子", BirthDay = DateTime.Parse("2000/10/11")}
        };
    }
}

3. Simple Injector の適用

3.1. NuGet パッケージの適用

ソリューション エクスプローラーで HelloWebApi プロジェクトを右クリックし「NuGet パッケージの管理(N)...」を選択します。

f:id:nuitsjp:20180215105338p:plain

開かれた NuGet パッケージマネージャー上で「参照」を選択し、検索テキストボックスに「SimpleInjector.Integration.WebApi.WebHost.QuickStart」を入力し、検索結果を選択して「インストール」を押下してください。

f:id:nuitsjp:20180215105634p:plain

インストールが完了すると、App_Start フォルダの下に SimpleInjectorWebApiInitializer が作成されていることが見て取れるはずです。

f:id:nuitsjp:20180215105723p:plain

3.2. DIコンテナへオブジェクトの登録

先ほど作成した注入対象のインターフェースとクラスをコンテナへ登録します。

SimpleInjectorWebApiInitializer を開いて、InitializeContainer メソッドをご覧ください。つぎのような記述があるはずです。

    private static void InitializeContainer(Container container)
    {
#error Register your services here (remove this line).

        // For instance:
        // container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
    }

ここの不要記述を削除し、対象オブジェクトのコンテナへの登録を行います。つぎのように記述しましょう。

private static void InitializeContainer(Container container)
{
    container.Register<IEmployeeRepository, EmployeeRepository>(Lifestyle.Scoped);
}

IEmployeeRepository インターフェースの注入を要求された箇所へ、EmployeeRepository クラスを注入するよう定義しました。

3.3. Controller へ IEmployeeRepository を注入する

それでは実際に注入する箇所を実装します。EmployeesController クラスを開いてください。つぎのような記述されています。

public class EmployeesController : ApiController
{
    // GET api/<controller>
    public IEnumerable<Employee> Get()
    {
        return new []
        {
            new Employee{Id = 1, Name = "山田 太郎", BirthDay = DateTime.Parse("1970/01/01")},
            new Employee{Id = 2, Name = "佐藤 花子", BirthDay = DateTime.Parse("2000/10/11")}
        };
    }

現在はController内で戻り値の生成を行なっていますが、これを IEmployeeRepository へ移譲するようつぎのように書き換えます。

public class EmployeesController : ApiController
{
    private readonly IEmployeeRepository _employeeRepository;

    public EmployeesController(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    // GET api/<controller>
    public IEnumerable<Employee> Get()
    {
        return _employeeRepository.GetEmoloyees();
    }

_employeeRepository フィールドで IEmployeeRepository インスタンスを保持しています。

Get メソッドの実装では_employeeRepository から返却する値を取得するように実装を修正しています。

_employeeRepository フィールドは、コンストラクタで引数として渡されたインスタンスを設定しています。コンストラクタへ IEmployeeRepository のインスタンスを渡すのは DI コンテナが行なってくれます。

実際にコンストラクタへデバッグポイントを設定して確認して見ましょう。

f:id:nuitsjp:20180215105857p:plain

ちゃんと EmployeeRepository クラスのインスタンスが渡されているのが見て取れるでしょう。もちろん正しく動作します。非常に簡単ですね。

なおController含め、利用されるインスタンスは全てリクエストの都度作成されます。

3.4. Lyfestyle を変更する

さて、改めて SimpleInjectorWebApiInitializer クラスの実装を見てみましょう。

public static class SimpleInjectorWebApiInitializer
{
    /// <summary>Initialize the container and register it as Web API Dependency Resolver.</summary>
    public static void Initialize()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
        ...

デフォルトでは WebApiRequestLifestyle が適用されています。しかし現時点では WebApiRequestLifestyle は非推奨となっており、 AsyncScopedLifestyle が推奨されています。

簡単に言いうと、AsyncScopedLifestyle を利用すると、非同期処理の先でもスコープが共有され、同じインスタンスが取得できるようになります。ただコードの中で Container#Resolve メソッドを利用していない場合は差異はありません。

それでは、つぎのようにコードを修正して Lifestyle を変更しましょう。

public static class SimpleInjectorWebApiInitializer
{
    /// <summary>Initialize the container and register it as Web API Dependency Resolver.</summary>
    public static void Initialize()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
        ...

これで Simple Injector が適用されました。今回はここまでになります。

次回は Web API 上で統合 Windows 認証を利用する方法を解説したいと思います。

www.nuits.jp