SignalR の依存関係挿入
警告
このドキュメントは、SignalR の最新バージョン用ではありません。 SignalR の ASP.NET Coreを見てみましょう。
このトピックで使用するソフトウェアのバージョン
- Visual Studio 2013
- .NET 4.5
- SignalR バージョン 2
このトピックの以前のバージョン
SignalR の以前のバージョンの詳細については、「 SignalR の古いバージョン」を参照してください。
質問とコメント
このチュートリアルが気に入った方法と、ページの下部にあるコメントで改善できる内容に関するフィードバックをお寄せください。 チュートリアルに直接関連しない質問がある場合は、 ASP.NET SignalR フォーラム または StackOverflow.com に投稿できます。
依存関係の挿入は、オブジェクト間のハードコーディングされた依存関係を削除する方法であり、(モック オブジェクトを使用して) テストしたり、実行時の動作を変更したりするために、オブジェクトの依存関係を簡単に置き換えることができます。 このチュートリアルでは、SignalR ハブで依存関係の挿入を実行する方法について説明します。 また、SignalR で IoC コンテナーを使用する方法も示します。 IoC コンテナーは、依存関係の挿入のための一般的なフレームワークです。
依存関係の挿入とは
依存関係の挿入に既に慣れている場合は、このセクションをスキップします。
依存関係の挿入 (DI) は、オブジェクトが独自の依存関係を作成する責任を負わないパターンです。 DI を動機付ける簡単な例を次に示します。 メッセージをログに記録する必要があるオブジェクトがあるとします。 ログ インターフェイスを定義できます。
interface ILogger
{
void LogMessage(string message);
}
オブジェクトでは、 を ILogger
作成してメッセージをログに記録できます。
// Without dependency injection.
class SomeComponent
{
ILogger _logger = new FileLogger(@"C:\logs\log.txt");
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
これは機能しますが、最適な設計ではありません。 を別ILogger
の実装に置き換えるFileLogger
場合は、 を変更SomeComponent
する必要があります。 他の多くのオブジェクトが を使用 FileLogger
していると仮定すると、それらのすべてを変更する必要があります。 または、シングルトンを作成 FileLogger
する場合は、アプリケーション全体で変更を加える必要もあります。
より良い方法は、 をオブジェクトに "挿入" ILogger
することです。たとえば、コンストラクター引数を使用します。
// With dependency injection.
class SomeComponent
{
ILogger _logger;
// Inject ILogger into the object.
public SomeComponent(ILogger logger)
{
if (logger == null)
{
throw new NullReferenceException("logger");
}
_logger = logger;
}
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
ここで、オブジェクトは、使用するを ILogger
選択する責任を負いません。 実装に依存するオブジェクトを変更せずに、実装を切り替 ILogger
えることができます。
var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);
このパターンは コンストラクターの挿入と呼ばれます。 もう 1 つのパターンはセッターインジェクションです。セッターメソッドまたはプロパティを使用して依存関係を設定します。
SignalR での単純な依存関係の挿入
SignalR を使用したチュートリアル はじめにの Chat アプリケーションについて考えてみましょう。 そのアプリケーションのハブ クラスを次に示します。
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
チャット メッセージを送信する前にサーバーに保存するとします。 この機能を抽象化するインターフェイスを定義し、DI を使用して インターフェイスを クラスに ChatHub
挿入できます。
public interface IChatRepository
{
void Add(string name, string message);
// Other methods not shown.
}
public class ChatHub : Hub
{
private IChatRepository _repository;
public ChatHub(IChatRepository repository)
{
_repository = repository;
}
public void Send(string name, string message)
{
_repository.Add(name, message);
Clients.All.addMessage(name, message);
}
唯一の問題は、SignalR アプリケーションがハブを直接作成しないことです。SignalR によって自動的に作成されます。 既定では、SignalR では、ハブ クラスにパラメーターなしのコンストラクターが必要です。 ただし、ハブ インスタンスを作成する関数を簡単に登録し、この関数を使用して DI を実行できます。 GlobalHost.DependencyResolver.Register を呼び出して関数を登録します。
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.Register(
typeof(ChatHub),
() => new ChatHub(new ChatMessageRepository()));
App.MapSignalR();
// ...
}
ここで、SignalR では、インスタンスを作成する必要がある場合は常に、この匿名関数が ChatHub
呼び出されます。
IoC コンテナー
前のコードは、単純なケースでは問題ありません。 しかし、あなたはまだこれを書かなければならなかった:
... new ChatHub(new ChatMessageRepository()) ...
多くの依存関係を持つ複雑なアプリケーションでは、この "配線" コードの多くを記述する必要がある場合があります。 このコードは、特に依存関係が入れ子になっている場合に、保守が困難な場合があります。 単体テストも困難です。
1 つの解決策は、IoC コンテナーを使用することです。 IoC コンテナーは、依存関係の管理を担当するソフトウェア コンポーネントです。コンテナーに型を登録し、コンテナーを使用してオブジェクトを作成します。 コンテナーは、依存関係関係を自動的に把握します。 多くの IoC コンテナーでは、オブジェクトの有効期間やスコープなどを制御することもできます。
Note
"IoC" は"制御の反転" を意味します。これは、フレームワークがアプリケーション コードを呼び出す一般的なパターンです。 IoC コンテナーによってオブジェクトが作成され、通常の制御フローが "反転" されます。
SignalR での IoC コンテナーの使用
チャット アプリケーションは、IoC コンテナーの利点を得るために単純すぎる可能性があります。 代わりに、 StockTicker サンプルを見てみましょう。
StockTicker サンプルでは、次の 2 つのメイン クラスが定義されています。
StockTickerHub
: クライアント接続を管理するハブ クラス。StockTicker
: 株価を保持し、定期的に更新するシングルトン。
StockTickerHub
はシングルトンへの参照を StockTicker
保持し StockTicker
、 の IHubConnectionContext への参照を StockTickerHub
保持します。 このインターフェイスを使用してインスタンスと StockTickerHub
通信します。 (詳細については、「 ASP.NET SignalR を使用したサーバー ブロードキャスト」を参照してください)。
IoC コンテナーを使用して、これらの依存関係を少しアンタングルすることができます。 まず、 クラスと StockTicker
クラスをStockTickerHub
簡略化しましょう。 次のコードでは、不要な部分をコメントアウトしました。
からパラメーターなしのコンストラクターを StockTickerHub
削除します。 代わりに、常に DI を使用してハブを作成します。
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly StockTicker _stockTicker;
//public StockTickerHub() : this(StockTicker.Instance) { }
public StockTickerHub(StockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
// ...
StockTicker の場合は、シングルトン インスタンスを削除します。 後で、IoC コンテナーを使用して StockTicker の有効期間を制御します。 また、コンストラクターをパブリックにします。
public class StockTicker
{
//private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
// () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
// Important! Make this constructor public.
public StockTicker(IHubConnectionContext<dynamic> clients)
{
if (clients == null)
{
throw new ArgumentNullException("clients");
}
Clients = clients;
LoadDefaultStocks();
}
//public static StockTicker Instance
//{
// get
// {
// return _instance.Value;
// }
//}
次に、 の StockTicker
インターフェイスを作成してコードをリファクタリングできます。 このインターフェイスを使用して、 を クラスからStockTicker
切り離StockTickerHub
します。
Visual Studio を使用すると、この種のリファクタリングが簡単になります。 StockTicker.cs ファイルを開き、クラス宣言を StockTicker
右クリックし、[ リファクタリング ] を選択します。 インターフェイスを抽出します。
[ インターフェイスの抽出 ] ダイアログで、[ すべて選択] をクリックします。 他の既定値はそのままにします。 [OK] をクリックします。
Visual Studio では、 という名前IStockTicker
の新しいインターフェイスが作成され、 からIStockTicker
派生するようにも変更StockTicker
されます。
IStockTicker.cs ファイルを開き、インターフェイスを パブリックに変更します。
public interface IStockTicker
{
void CloseMarket();
IEnumerable<Stock> GetAllStocks();
MarketState MarketState { get; }
void OpenMarket();
void Reset();
}
クラスで、 の StockTickerHub
2 つのインスタンスを StockTicker
に IStockTicker
変更します。
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly IStockTicker _stockTicker;
public StockTickerHub(IStockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
インターフェイスの IStockTicker
作成は厳密には必要ではありませんが、DI がアプリケーション内のコンポーネント間の結合を減らすのにどのように役立つかを示したいと思いました。
Ninject ライブラリを追加する
.NET 用の多くのオープンソース IoC コンテナーがあります。 このチュートリアルでは、 Ninject を使用します。 (その他の一般的なライブラリには、 Castle Windsor、 Spring.Net、 Autofac、 Unity、 StructureMap などがあります)。
NuGet パッケージ マネージャーを使用して 、Ninject ライブラリをインストールします。 Visual Studio の [ツール] メニューから [NuGet パッケージ マネージャー パッケージ マネージャー> コンソール] を選択します。 [パッケージ マネージャー コンソール] ウィンドウで、次のコマンドを入力します。
Install-Package Ninject -Version 3.0.1.10
SignalR 依存関係リゾルバーを置き換える
SignalR 内で Ninject を使用するには、 DefaultDependencyResolver から派生するクラスを作成します。
internal class NinjectSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IKernel _kernel;
public NinjectSignalRDependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
public override object GetService(Type serviceType)
{
return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType));
}
}
このクラスは、DefaultDependencyResolver の GetService メソッドと GetServices メソッドをオーバーライドします。 SignalR は、これらのメソッドを呼び出して、実行時にさまざまなオブジェクト (ハブ インスタンスなど) と、SignalR によって内部的に使用されるさまざまなサービスを作成します。
- GetService メソッドは、型の 1 つのインスタンスを作成します。 Ninject カーネルの TryGet メソッドを呼び出すには、このメソッドをオーバーライドします。 そのメソッドが null を返す場合は、既定のリゾルバーにフォールバックします。
- GetServices メソッドは、指定した型のオブジェクトのコレクションを作成します。 このメソッドをオーバーライドして、Ninject の結果を既定のリゾルバーの結果と連結します。
Ninject バインドを構成する
次に、Ninject を使用して型バインドを宣言します。
アプリケーションの Startup.cs クラスを開きます (のパッケージの手順に従って手動で readme.txt
作成したか、認証をプロジェクトに追加して作成したクラス)。 メソッドで Startup.Configuration
、Ninject コンテナーを作成します。このコンテナーは Ninject によってカーネルが呼び出 されます。
var kernel = new StandardKernel();
カスタム依存関係リゾルバーのインスタンスを作成します。
var resolver = new NinjectSignalRDependencyResolver(kernel);
のバインド IStockTicker
を次のように作成します。
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
このコードでは、2 つのことを言います。 まず、アプリケーションで が必要な IStockTicker
場合は常に、 のインスタンス StockTicker
をカーネルで作成する必要があります。 次に、 クラスを StockTicker
シングルトン オブジェクトとして作成する必要があります。 Ninject は オブジェクトの 1 つのインスタンスを作成し、要求ごとに同じインスタンスを返します。
次のように IHubConnectionContext のバインドを作成します。
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
このコードでは、 IHubConnection を返す匿名関数を作成します。 WhenInjectedInto メソッドは、インスタンスの作成時IStockTicker
にのみこの関数を使用するように Ninject に指示します。 その理由は、SignalR によって IHubConnectionContext インスタンスが内部的に作成され、SignalR によって作成される方法をオーバーライドしたくないためです。 この関数は、クラス StockTicker
にのみ適用されます。
ハブ構成を追加して、依存関係リゾルバーを MapSignalR メソッドに渡します。
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
サンプルの Startup クラスの Startup.ConfigureSignalR メソッドを新しいパラメーターで更新します。
public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
{
app.MapSignalR(config);
}
SignalR では、既定のリゾルバーではなく、 MapSignalR で指定されたリゾルバーが使用されるようになりました。
の完全なコード一覧を次に Startup.Configuration
示します。
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
var kernel = new StandardKernel();
var resolver = new NinjectSignalRDependencyResolver(kernel);
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
}
}
Visual Studio で StockTicker アプリケーションを実行するには、F5 キーを押します。 ブラウザー ウィンドウで、 に移動します http://localhost:*port*/SignalR.Sample/StockTicker.html
。
アプリケーションの機能は以前とまったく同じです。 (説明については、「 ASP.NET SignalR を使用したサーバー ブロードキャスト」を参照してください)。動作は変更されていません。により、コードのテスト、保守、および進化が容易になりました。