SignalR 1.x の依存関係挿入

作成者: Patrick Fletcher

警告

このドキュメントは、SignalR の最新バージョン用ではありません。 SignalR の ASP.NET Coreを見てみましょう。

依存関係の挿入は、オブジェクト間のハードコーディングされた依存関係を削除する方法であり、(モック オブジェクトを使用して) テストしたり、実行時の動作を変更したりするために、オブジェクトの依存関係を簡単に置き換えることができます。 このチュートリアルでは、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 を呼び出して関数を登録します。

protected void Application_Start()
{
    GlobalHost.DependencyResolver.Register(
        typeof(ChatHub), 
        () => new ChatHub(new ChatMessageRepository()));

    RouteTable.Routes.MapHubs();

    // ...
}

ここで、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簡略化しましょう。 次のコードでは、不要な部分をコメントアウトしました。

からパラメーターなしのコンストラクターを StockTicker削除します。 代わりに、常に 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 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 右クリックし、[ リファクタリング ] を選択します。 インターフェイスを抽出します

Visual Studio Code 上に表示されている右クリック ドロップダウン メニューのスクリーンショット。[リファクタリング] オプションと [インターフェイスの抽出] オプションが強調表示されています。

[ インターフェイスの抽出 ] ダイアログで、[ すべて選択] をクリックします。 他の既定値はそのままにします。 [OK] をクリックします。

[インターフェイスの抽出] ダイアログのスクリーンショット。[すべて選択] オプションが強調表示され、[O K] オプションが表示されています。

Visual Studio では、 という名前IStockTickerの新しいインターフェイスが作成され、 からIStockTicker派生するようにも変更StockTickerされます。

IStockTicker.cs ファイルを開き、インターフェイスを パブリックに変更します。

public interface IStockTicker
{
    void CloseMarket();
    IEnumerable<Stock> GetAllStocks();
    MarketState MarketState { get; }
    void OpenMarket();
    void Reset();
}

クラスで、 の StockTickerHub 2 つのインスタンスを StockTickerIStockTicker変更します。

[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 WindsorSpring.NetAutofacUnityStructureMap などがあります)。

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));
    }
}

このクラスは、DefaultDependencyResolverGetService メソッドと GetServices メソッドをオーバーライドします。 SignalR は、これらのメソッドを呼び出して、実行時にさまざまなオブジェクト (ハブ インスタンスを含む) と、SignalR によって内部的に使用されるさまざまなサービスを作成します。

  • GetService メソッドは、型の単一のインスタンスを作成します。 Ninject カーネルの TryGet メソッドを呼び出すには、このメソッドをオーバーライドします。 そのメソッドが null を返す場合は、既定のリゾルバーにフォールバックします。
  • GetServices メソッドは、指定した型のオブジェクトのコレクションを作成します。 Ninject の結果と既定の競合回避モジュールの結果を連結するには、このメソッドをオーバーライドします。

Ninject バインドを構成する

次に、Ninject を使用して型バインディングを宣言します。

RegisterHubs.cs ファイルを開きます。 メソッドで RegisterHubs.Start 、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<IHubConnectionContext>().ToMethod(context =>
    resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();

このコードでは、 IHubConnection を返す匿名関数を作成します。 WhenInjectedInto メソッドは、インスタンスの作成時IStockTickerにのみこの関数を使用するように Ninject に指示します。 その理由は、SignalR によって IHubConnectionContext インスタンスが内部的に作成され、SignalR によって作成される方法をオーバーライドしたくないためです。 この関数は、クラス StockTicker にのみ適用されます。

依存関係リゾルバーを MapHubs メソッドに渡します。

RouteTable.Routes.MapHubs(config);

SignalR では、既定のリゾルバーではなく、 MapHubs で指定されたリゾルバーが使用されるようになりました。

の完全なコード一覧を次に RegisterHubs.Start示します。

public static class RegisterHubs
{
    public static void Start()
    {
        var kernel = new StandardKernel();
        var resolver = new NinjectSignalRDependencyResolver(kernel);

        kernel.Bind<IStockTicker>()
            .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()
            .InSingletonScope();

        kernel.Bind<IHubConnectionContext>().ToMethod(context =>
                resolver.Resolve<IConnectionManager>().
                    GetHubContext<StockTickerHub>().Clients
            ).WhenInjectedInto<IStockTicker>();

        var config = new HubConfiguration()
        {
            Resolver = resolver
        };

        // Register the default hubs route: ~/signalr/hubs
        RouteTable.Routes.MapHubs(config);
    }
}

Visual Studio で StockTicker アプリケーションを実行するには、F5 キーを押します。 ブラウザー ウィンドウで、 に移動します http://localhost:*port*/SignalR.Sample/StockTicker.html

インターネット エクスプローラー ブラウザー ウィンドウに表示されている A S P ドット NET Signal R Stock Ticker サンプル画面のスクリーンショット。

アプリケーションの機能は以前とまったく同じです。 (説明については、「 ASP.NET SignalR を使用したサーバー ブロードキャスト」を参照してください)。動作は変更されていません。により、コードのテスト、保守、および進化が容易になりました。