最初に補足
コントローラー以外でもDI連携する方法について完全版も記述したので、そちらも参照してください
やること
ASP.NET CoreではDependency Injectionが標準機能として組み込まれていますが、それでは機能不足な事も多いと思います。
そこで、他のDIコンテナとASP.NET Coreを連携させる方法について記述します。
通常、各種DIコンテナについては、その拡張としてASP.NET Coreとのインテグレーション機能が提供されると思いますが、今回は自作のDependency Resolverを使用して、連携方法の仕組み自体について記述します。
この方法と同じやり方をすることで、インテグレーション機能が提供されていないコンテナについても、ASP.NET Coreとの連携が可能となります。
環境
- Visual Strudio 2015
- .NET Core Tooling Preview 2 for Visual Studio 2015
- Smart.Resolver 1.0.4 (自作のGuice型Dependency Resolver)
前提
ASP.NET Core Web Applicationを作成し、NuGetでUsa.Smart.Resolverを追加しておきます。
手順
IControllerActivator実装
IControllerActivatorの派生クラスとして、以下のような実装を用意します。
なお、IResolverはSmart.ResolverのDependency Resolverインターフェースになります。
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Smart.Resolver;
public class SmartResolverControllerActivator : IControllerActivator
{
private readonly IResolver resolver;
public SmartResolverControllerActivator(IResolver resolver)
{
this.resolver = resolver;
}
public object Create(ControllerContext context)
{
return resolver.Get(context.ActionDescriptor.ControllerTypeInfo.AsType());
}
public void Release(ControllerContext context, object controller)
{
(controller as IDisposable)?.Dispose();
}
}
Startup修正
StartupクラスでStandardResolverをメンバに定義し、ConfigureServices()でSmartResolverControllerActivatorの設定を行います。
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Smart.Resolver;
public class Startup
{
private readonly StandardResolver resolver = new StandardResolver();
...
public void ConfigureServices(IServiceCollection services)
{
// 標準のASP.NET設定はここ
services.AddSingleton<IControllerActivator>(new SmartResolverControllerActivator(resolver));
}
...
}
IControllerActivatorが設定されると、コントローラーのインスタンスはその実装経由で行われるようになります。
この例では、実際のインスタンスの生成をSmart.ResolverのDependency Resolver実装であるStandardResolverに委譲することで、ASP.NET CoreとSmart.Resolverの連携を実現しています。
サンプル
実際に動くサンプルを以下に用意しました。
このサンプルは、用途毎に複数のDB接続があるようなアプリケーションを想定したものです。
接続を生成する複数のIConnectionFactoryのインスタンスについて、ASP.NET Coreの標準ではサポートされない条件付きバインディングを行っています。
なお、SQLiteとDapperを使用し、実際にデータアクセスまで行っています。
インスタンス生成委譲対象
サンプルで、StandardResolverにインスタンスの生成/管理を委譲する主なクラスについて以下に記述します。
クラス | スコープ | 概要 |
---|---|---|
CharacterController | Prototype | CharacterServiceを使用するAPIコントローラー |
ItemController | Prototype | ItemServiceを使用するAPIコントローラー |
CharacterService | Singleton | 名称"Character"のCallbackConnectionFactoryを使用する |
ItemService | Singleton | 名称"Master"のCallbackConnectionFactoryを使用する |
IConnectionFactory | Singleton * 2 | "Character"、"Master"の2つのインスタンスが存在する |
Resolver初期化コード
Resolverの初期化コードを以下に抜粋します。
var connectionStringMaster = Configuration.GetConnectionString("Master");
resolver
.Bind<IConnectionFactory>()
.ToConstant(new CallbackConnectionFactory(() => new SqliteConnection(connectionStringMaster)))
.Named("Master");
var connectionStringCharacter = Configuration.GetConnectionString("Character");
resolver
.Bind<IConnectionFactory>()
.ToConstant(new CallbackConnectionFactory(() => new SqliteConnection(connectionStringCharacter)))
.Named("Character");
resolver
.Bind<MasterService>()
.ToSelf()
.InSingletonScope()
.WithConstructorArgument("connectionFactory", kernel => kernel.Get<IConnectionFactory>("Master"));
resolver
.Bind<CharacterService>()
.ToSelf()
.InSingletonScope()
.WithConstructorArgument("connectionFactory", kernel => kernel.Get<IConnectionFactory>("Character"));
CallbackConnectionFactoryについては、接続情報の異なる2つのインスタンスを、Named()メソッドにより異なる名称で登録しています。
2つのServiceクラスについては、Singletonスコープとして登録し、インスタンス生成時のコンストラクタ引数connectionFactoryについて、名称指定でResolverから取得して設定するようにしています。
なお、Named()及びWithConstructorArgument()によって条件付きバインディングを行っていますが、名称による条件付きバインディングはNamedAttributeを使う事でも可能です。
また、Smart.Resolverでは明示的に情報を登録しないクラスについてはPrototypeスコープとして扱われるため、コントローラーについてはResolverへの情報登録は不要となっています。
うさコメ
IControllerActivatorの他に、IViewComponentActivatorとかもありますでよ(・ω・)
参考文献
- 作って理解するDependency Resolver(なぜかDIコンテナと言わないというこだわり) - Smart.Resolverに関する落書きメモ