SignalR 1.x の依存関係挿入Dependency Injection in SignalR 1.x

によってMike WassonPatrick Fletcherby Mike Wasson, Patrick Fletcher

Note

この記事では、ASP.NET SignalR を指します。This article refers to ASP.NET SignalR. SignalR を使用して、Java、Node.js、またはサーバーレス シナリオでは、リアルタイムのシナリオを有効にする方法と思う場合を見てASP.NET Core SignalRします。If you're thinking about using SignalR to enable real-time scenarios with Java, Node.js, or in a serverless scenario, take a look at ASP.NET Core SignalR. 既に ASP.NET SignalR を使用した場合を見て、のバージョンの違いバージョンの違いと ASP.NET Core SignalR での機能強化を理解するページ。If you've already used ASP.NET SignalR, take a look at the version differences page to understand the differences in the versions and the improvements in ASP.NET Core SignalR. 最後に、Microsoft Azure でリアルタイム アプリを実行することがわかっている場合を見て、 Azure SignalR サービスなど、アプリを必要とすると、クラウド ベースのスケール アウトを提供します。Finally, if you know you'll be running your real-time apps in Microsoft Azure, take a look at the Azure SignalR Service, as it provides cloud-based scale-out once your apps need it.

依存関係の挿入は、簡単になります (モック オブジェクトを使用して) テストのいずれかのオブジェクトの依存関係を置換するか、実行時の動作を変更するオブジェクト間の依存関係をハードコーディングを削除する方法です。Dependency injection is a way to remove hard-coded dependencies between objects, making it easier to replace an object's dependencies, either for testing (using mock objects) or to change run-time behavior. このチュートリアルでは、SignalR ハブの依存関係の挿入を実行する方法を示します。This tutorial shows how to perform dependency injection on SignalR hubs. SignalR で IoC コンテナーを使用する方法も示します。It also shows how to use IoC containers with SignalR. IoC コンテナーは、依存関係の挿入の一般的なフレームワークです。An IoC container is a general framework for dependency injection.

依存関係の挿入とは何ですか。What is Dependency Injection?

依存関係の挿入を理解している場合は、このセクションをスキップします。Skip this section if you are already familiar with dependency injection.

依存関係の注入(DI) は、パターン オブジェクトが独自の依存関係の作成を担当します。Dependency injection (DI) is a pattern where objects are not responsible for creating their own dependencies. DI を顧客が簡単な例を次に示します。Here is a simple example to motivate DI. メッセージを記録する必要があるオブジェクトがあるとします。Suppose you have an object that needs to log messages. ログ記録のインターフェイスを定義する場合があります。You might define a logging interface:

interface ILogger 
{
    void LogMessage(string message);
}

オブジェクトを作成できます、ILoggerメッセージを記録します。In your object, you can create an ILogger to log messages:

// Without dependency injection.
class SomeComponent
{
    ILogger _logger = new FileLogger(@"C:\logs\log.txt");

    public void DoSomething()
    {
        _logger.LogMessage("DoSomething");
    }
}

これは、動作しますが、最適なデザインではありません。This works, but it's not the best design. 置換する場合FileLoggerILoggerの実装を変更する必要があるSomeComponentします。If you want to replace FileLogger with another ILogger implementation, you will have to modify SomeComponent. 多くの他のオブジェクトで使用されるとFileLogger、それらのすべてを変更する必要があります。Supposing that a lot of other objects use FileLogger, you will need to change all of them. 作成する場合またはFileLoggerシングルトンもする必要があります、アプリケーション全体での変更を加えます。Or if you decide to make FileLogger a singleton, you'll also need to make changes throughout the application.

「注入」するほうが効果的、ILoggerオブジェクトに、たとえば、コンス トラクター引数を使用しています。A better approach is to "inject" an ILogger into the object—for example, by using a constructor argument:

// 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を使用します。Now the object is not responsible for selecting which ILogger to use. 切り替えることができますILoggerそれに依存するオブジェクトを変更することがなく実装します。You can switch ILogger implementations without changing the objects that depend on it.

var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);

このパターンと呼ばれますコンス トラクターの挿入します。This pattern is called constructor injection. パターンの 1 つは、セッターの挿入、setter メソッドまたはプロパティから依存関係を設定します。Another pattern is setter injection, where you set the dependency through a setter method or property.

SignalR で単純な依存関係の挿入Simple Dependency Injection in SignalR

チュートリアルのチャット アプリケーションを考えますSignalR の概要します。Consider the Chat application from the tutorial Getting Started with SignalR. そのアプリケーションからハブ クラスを次に示します。Here is the hub class from that application:

public class ChatHub : Hub
{
    public void Send(string name, string message)
    {
        Clients.All.addMessage(name, message);
    }
}

チャット メッセージを送信する前にサーバーに格納するとします。Suppose that you want to store chat messages on the server before sending them. この機能は、抽象化するインターフェイスを定義してにインターフェイスを挿入する DI を使用する場合があります、ChatHubクラス。You might define an interface that abstracts this functionality, and use DI to inject the interface into the ChatHub class.

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 を作成します。The only problem is that a SignalR application does not directly create hubs; SignalR creates them for you. 既定では、SignalR には、パラメーターなしのコンス トラクターを持つハブ クラスが必要です。By default, SignalR expects a hub class to have a parameterless constructor. ただし、ハブのインスタンスを作成する関数を登録して、この関数を使用して、DI を実行することができます簡単にします。However, you can easily register a function to create hub instances, and use this function to perform DI. 関数を呼び出すことによって登録GlobalHost.DependencyResolver.Registerします。Register the function by calling GlobalHost.DependencyResolver.Register.

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

    RouteTable.Routes.MapHubs();

    // ...
}

SignalR は、作成する必要があるたびに、この匿名関数が呼び出すので、ChatHubインスタンス。Now SignalR will invoke this anonymous function whenever it needs to create a ChatHub instance.

IoC コンテナーIoC Containers

前のコードは、単純なケースに適してします。The previous code is fine for simple cases. これを記述する必要があります。But you still had to write this:

... new ChatHub(new ChatMessageRepository()) ...

多くの依存関係を持つ複雑なアプリケーションでは、多くの「接続」このコードを記述する必要があります。In a complex application with many dependencies, you might need to write a lot of this "wiring" code. このコードの依存関係が入れ子になった場合に特にハードを維持することはできます。This code can be hard to maintain, especially if dependencies are nested. また、単体テストは困難です。It is also hard to unit test.

1 つのソリューションでは、IoC コンテナーを使用します。One solution is to use an IoC container. IoC コンテナーは、依存関係の管理を担当するソフトウェア コンポーネントです。型、コンテナーを登録し、コンテナーを使用して、オブジェクトを作成します。An IoC container is a software component that is responsible for managing dependencies.You register types with the container, and then use the container to create objects. コンテナーは、依存関係を自動的に検出します。The container automatically figures out the dependency relations. 多くの IoC コンテナーでは、オブジェクトの有効期間とスコープなどを制御することもできます。Many IoC containers also allow you to control things like object lifetime and scope.

Note

"IoC"「の制御の反転」の略フレームワークからアプリケーション コードを呼び出す、一般的なパターンは。"IoC" stands for "inversion of control", which is a general pattern where a framework calls into application code. IoC コンテナーを構築、オブジェクト、コントロールの通常のフローの「反転」をします。An IoC container constructs your objects for you, which "inverts" the usual flow of control.

SignalR の IoC コンテナーの使用Using IoC Containers in SignalR

チャット アプリケーションは、IoC コンテナーのメリットは単純すぎる可能性があります。The Chat application is probably too simple to benefit from an IoC container. 代わりを見てみましょう、 StockTickerサンプル。Instead, let's look at the StockTicker sample.

StockTicker サンプルでは、2 つの主要なクラスを定義します。The StockTicker sample defines two main classes:

  • StockTickerHub:ハブ クラスはクライアント接続を管理します。: The hub class, which manages client connections.
  • StockTicker:株価を保持し、定期的に更新するシングルトン。: A singleton that holds stock prices and periodically updates them.

StockTickerHub 参照を保持、StockTickerシングルトン、中にStockTickerへの参照を保持、 IHubConnectionContextStockTickerHubします。holds a reference to the StockTicker singleton, while StockTicker holds a reference to the IHubConnectionContext for the StockTickerHub. このインターフェイスを使用して通信をStockTickerHubインスタンス。It uses this interface to communicate with StockTickerHub instances. (詳細については、次を参照してくださいASP.NET SignalR によるサーバー ブロードキャスト。)。(For more information, see Server Broadcast with ASP.NET SignalR.)

これらの依存関係を少し整理して、IoC コンテナーを使用できます。We can use an IoC container to untangle these dependencies a bit. まず、少し簡略化し、StockTickerHubStockTickerクラス。First, let's simplify the StockTickerHub and StockTicker classes. 次のコードではコメント部分にしない必要があります。In the following code, I've commented out the parts that we don't need.

パラメーターなしのコンス トラクターを削除StockTickerします。Remove the parameterless constructor from StockTicker. 代わりに、ハブを作成するのに DI を常に使用します。Instead, we will always use DI to create the hub.

[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 シングルトン インスタンスを削除します。For StockTicker, remove the singleton instance. その後、StockTicker 有効期間を制御、IoC コンテナーを使用します。Later, we'll use the IoC container to control the StockTicker lifetime. パブリック コンス トラクターは、また、ください。Also, make the constructor public.

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します。Next, we can refactor the code by creating an interface for StockTicker. このインターフェイスを使用して、分離、StockTickerHubから、StockTickerクラス。We'll use this interface to decouple the StockTickerHub from the StockTicker class.

Visual Studio でこの種のリファクタリングも簡単。Visual Studio makes this kind of refactoring easy. StockTicker.cs ファイルを開きを右クリックし、StockTickerクラス宣言、および選択リファクタリング.インターフェイスの抽出します。Open the file StockTicker.cs, right-click on the StockTicker class declaration, and select Refactor ... Extract Interface.

インターフェイスの抽出ダイアログ ボックスで、をクリックしてすべて選択します。In the Extract Interface dialog, click Select All. その他の既定値のままにします。Leave the other defaults. [OK] をクリックします。Click OK.

Visual Studio は、という名前の新しいインターフェイスを作成します。 IStockTicker、し、変更もStockTickerから派生するIStockTickerします。Visual Studio creates a new interface named IStockTicker, and also changes StockTicker to derive from IStockTicker.

IStockTicker.cs ファイルを開き、インターフェイスを変更パブリックします。Open the file IStockTicker.cs and change the interface to public.

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

StockTickerHubクラス、2 つのインスタンスを変更するStockTickerIStockTicker:In the StockTickerHub class, change the two instances of StockTicker to 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、アプリケーションのコンポーネント間の結合の削減を支援する方法を表示したいです。Creating an IStockTicker interface isn't strictly necessary, but I wanted to show how DI can help to reduce coupling between components in your application.

Ninject ライブラリを追加します。Add the Ninject Library

.NET の多くのオープン ソース IoC コンテナーがあります。There are many open-source IoC containers for .NET. このチュートリアルで使用しますNinjectします。For this tutorial, I'll use Ninject. (その他の一般的なライブラリを含めるCastle WindsorSpring.NetAutofacUnity、およびStructureMap.)(Other popular libraries include Castle Windsor, Spring.Net, Autofac, Unity, and StructureMap.)

NuGet パッケージ マネージャーを使用して、インストール、 Ninject ライブラリします。Use NuGet Package Manager to install the Ninject library. Visual Studio から、ツールメニューの NuGet パッケージ マネージャー > パッケージ マネージャー コンソールします。In Visual Studio, from the Tools menu select NuGet Package Manager > Package Manager Console. パッケージ マネージャー コンソール ウィンドウで、次のコマンドを入力します。In the Package Manager Console window, enter the following command:

Install-Package Ninject -Version 3.0.1.10

SignalR の依存関係競合回避モジュールを置き換えますReplace the SignalR Dependency Resolver

SignalR 内 Ninject を使用するから派生したクラスを作成DefaultDependencyResolverします。To use Ninject within SignalR, create a class that derives from 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));
    }
}

このクラスのオーバーライド、 GetServiceGetServicesメソッドのDefaultDependencyResolverします。This class overrides the GetService and GetServices methods of DefaultDependencyResolver. SignalR ハブのインスタンスの SignalR によって内部的に使用されるさまざまなサービスなど、実行時にさまざまなオブジェクトを作成するこれらのメソッドを呼び出します。SignalR calls these methods to create various objects at runtime, including hub instances, as well as various services used internally by SignalR.

  • GetServiceメソッドは、型の 1 つのインスタンスを作成します。The GetService method creates a single instance of a type. このメソッドを呼び出す、Ninject カーネルのオーバーライドTryGetメソッド。Override this method to call the Ninject kernel's TryGet method. そのメソッドが null を返す場合は、既定の競合回避モジュールに戻ります。If that method returns null, fall back to the default resolver.
  • GetServicesメソッドは、指定した型のオブジェクトのコレクションを作成します。The GetServices method creates a collection of objects of a specified type. 既定のリゾルバーからの結果には、Ninject からの結果を連結するには、このメソッドをオーバーライドします。Override this method to concatenate the results from Ninject with the results from the default resolver.

Ninject バインドを構成します。Configure Ninject Bindings

宣言型バインディングに Ninject を使用します。Now we'll use Ninject to declare type bindings.

RegisterHubs.cs ファイルを開きます。Open the file RegisterHubs.cs. RegisterHubs.Startメソッド、Ninject 呼び出す Ninject コンテナーを作成、カーネルします。In the RegisterHubs.Start method, create the Ninject container, which Ninject calls the kernel.

var kernel = new StandardKernel();

このカスタム依存関係競合回避モジュールのインスタンスを作成します。Create an instance of our custom dependency resolver:

var resolver = new NinjectSignalRDependencyResolver(kernel);

バインドを作成IStockTicker次のようにします。Create a binding for IStockTicker as follows:

kernel.Bind<IStockTicker>()
    .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()  // Bind to StockTicker.
    .InSingletonScope();  // Make it a singleton object.

このコードは次の 2 つことを示しています。This code is saying two things. 最初に、アプリケーションに必要なときに、 IStockTicker、カーネルがのインスタンスを作成する必要がありますStockTickerします。First, whenever the application needs an IStockTicker, the kernel should create an instance of StockTicker. 2 番目、StockTickerクラスはシングルトン オブジェクトとして作成する必要があります。Second, the StockTicker class should be a created as a singleton object. Ninject では、オブジェクトの 1 つのインスタンスを作成し、要求ごとに同じインスタンスを返します。Ninject will create one instance of the object, and return the same instance for each request.

バインドを作成IHubConnectionContext次のようにします。Create a binding for IHubConnectionContext as follows:

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

このコードを返す匿名関数の作成、 IHubConnectionします。This code creates an anonymous function that returns an IHubConnection. WhenInjectedIntoメソッドに指示を作成するときにのみ、この関数を使用する NinjectIStockTickerインスタンス。The WhenInjectedInto method tells Ninject to use this function only when creating IStockTicker instances. 理由は、SignalR が作成されるIHubConnectionContextインスタンスを内部的には、SignalR での作成方法をオーバーライドする必要はないです。The reason is that SignalR creates IHubConnectionContext instances internally, and we don't want to override how SignalR creates them. この関数にのみ適用されます、StockTickerクラス。This function only applies to our StockTicker class.

依存関係の競合回避モジュールに渡す、 MapHubsメソッド。Pass the dependency resolver into the MapHubs method:

RouteTable.Routes.MapHubs(config);

SignalR がで指定された競合回避モジュールを使用してMapHubs既定のリゾルバーではなく。Now SignalR will use the resolver specified in MapHubs, instead of the default resolver.

完全なコードを次に示しますRegisterHubs.Startします。Here is the complete code listing for 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 キーを押します。To run the StockTicker application in Visual Studio, press F5. ブラウザーのウィンドウでに移動します。http://localhost:*port*/SignalR.Sample/StockTicker.htmlします。In the browser window, navigate to http://localhost:*port*/SignalR.Sample/StockTicker.html.

アプリケーションには、正確に前に、のと同じ機能があります。The application has exactly the same functionality as before. (詳細については、次を参照してくださいASP.NET SignalR によるサーバー ブロードキャスト。)。動作を変更していません。テスト、保守、および進化を簡単にコードを単になりました。(For a description, see Server Broadcast with ASP.NET SignalR.) We haven't changed the behavior; just made the code easier to test, maintain, and evolve.