ASP.NET Core MVC のDIコンテナを利用する Part.1

前回までのあらすじ

DIを
使おう

mrgchr.hatenablog.com

とりあえず、実行してみる

前回は、DIパターンの抽出まで確認しました。
で、この状態ではコントローラーのコンストラクタに何を渡せばよいのか、フレームワークでは解決できずエラーになります。

とりあえず起動してみてみましょう。ビューとして"\Views\Nice\Index.cs"を下記のように作っておきます。

<p>DI GetValue : @ViewData["value"]</p>

"/Nice/Index"にアクセスすると下記のようなエラーが出ます。

An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'PracticeWorld1.Services.IValueService' while attempting to activate 'PracticeWorld1.Controllers.NiceController'.

「IValueServiceに何を渡せばよいかわかりませんでした」とのことです。
うん、納得できる。

DIコンテナを設定する

ASP.NET Core MVC標準のDIコンテナを利用するためには、Starup.csのConfigureServicesメソッドを編集する必要があります。

//Startup.cs

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  services.AddTransient<IValueService, ValueService>();

  // Add framework services.
  services.AddApplicationInsightsTelemetry(Configuration);

  services.AddMvc();
}

"services.AddTransient<IValueService, ValueService>();"の部分がDIコンテナを設定している箇所です。
この場合は、「IValueServiceが必要な時はValueServiceのインスタンスをインジェクトしてやー」という感じになります。

また、DIコンテナに関わるメソッドは"AddTransient"のほかにも、"AddScoped"や"AddSingleton"などがあるのですが、これらの違いはインジェクトするオブジェクトのライフサイクルの違いとなります。
これらについては次回に書きたいと思います。

実行する

Startup.csにDIコンテナの設定をした後に、再度実行し同様にアクセスすると、

f:id:mrgchr:20161021000218p:plain

出来ました!エラーを吐かずにちゃんと動作しています。

インジェクションの種類

さて、今までの例ではコントローラーのコンストラクタに対してのインジェクションを扱ってきましたが、それ以外の状況でもDIを利用できます。

コンストラクタインジェクション

例でも見たように、コンストラクタに対してインジェクションを行うものです。
インジェクトするオブジェクトは一つしかダメなどという制限はなく、下記のように一度に複数のインジェクションを行うこともできます。

private readonly IValueService _service1;
private readonly IValueService _service2;

public NiceController(IValueService service1, IValueService service2)
{
  _service1 = service1;
  _service2 = service2;
}

また、AddTransientでDIを設定した場合は、service1とservice2は異なるオブジェクトになります。

アクションインジェクション

アクションインジェクションはその名の通り、アクション実行時にDIコンテナからインスタンスを渡す方法です。
下記のように、パラメータに[FromServices]属性を付けることで利用可能です。

public IActionResult Index([FromServices]IValueService service)
{
  ViewData["constructorInject1"] = _service1.GetValue();
  ViewData["constructorInject2"] = _service2.GetValue();
  ViewData["actionInject"] = service.GetValue();
  return View();
}

例えば、「あるアクションでだけ必要なサービスがあるけど、それ以外のアクションでは必要としないのでコンストラクタインジェクションでインジェクトしたくはない」といった時に使えるかもしれません。

ビューインジェクション

ビューでもインジェクションを行うことができます。
下記のように "@inject [サービスの型] [変数名]" とすることでRazorに対してもインジェクションが実行されます。

@inject PracticeWorld1.Services.IValueService service

<p>Constructor1 GetValue : @ViewData["constructorInject1"]</p>
<p>Constructor2 GetValue : @ViewData["constructorInject2"]</p>
<p>Action GetValue : @ViewData["actionInject"]</p>
<p>View GetValue : @service.GetValue()</p>

ビューに対してインジェクションを行うにしてもどのように使うのか有効なのかすぐには分からないのですが、ローカリゼーション対応などに利用することを想定しているようです。

その他

Betaの段階ではプロパティに対してもインジェクションを行うことが出来たようですが、この機能は削除されたとのことです。

最後に

上記を動かしてみると下記のようになりました。

f:id:mrgchr:20161021002824p:plain

こんな感じで正常に動いているようです。
コンストラクタ、アクション、ビューで渡されたオブジェクトがそれぞれ別のものであることも確認できます。

そのあたりのライフサイクルの話は次回に。