ASP.NET SignalR Hubs API ガイド - サーバー (SignalR 1.x)

作成者: Patrick FletcherTom Dykstra

警告

このドキュメントは、最新版の SignalR を対象としていません。 ASP.NET Core SignalR に関する記事を参照してください。

このドキュメントでは、SignalR バージョン 1.1 用の ASP.NET SignalR Hubs API のサーバー側でのプログラミングの概要と、一般的なオプションを示すコード サンプルについて説明します。

SignalR Hubs API を使うと、サーバーから接続されたクライアントに、およびクライアントからサーバーに、リモート プロシージャ コール (RPC) を行うことができます。 サーバー コードでは、クライアントから呼び出すことができるメソッドを定義し、クライアント上で実行されるメソッドを呼び出します。 クライアント コードでは、サーバーから呼び出すことができるメソッドを定義し、サーバー上で実行されるメソッドを呼び出します。 クライアントとサーバーの間のやり取りはすべて、SignalR によって自動的に処理されます。

SignalR は、永続的接続と呼ばれる下位レベルの API も提供します。 SignalR、Hubs、永続的な接続の概要について、または完全な SignalR アプリケーションの構築方法を説明するチュートリアルについては、SignalR の概要を参照してください。

概要

このドキュメントは、次のトピックに分かれています。

クライアントのプログラミング方法に関するドキュメントについては、次のリソースを参照してください。

API リファレンス トピックへのリンクは、API の .NET 4.5 バージョンへのリンクです。 .NET 4 を使用している場合は、.NET 4 バージョンの API トピックを参照してください。

SignalR ルートを登録し、SignalR オプションを構成する方法

クライアントがハブへの接続に使用するルートを定義するには、アプリケーションの起動時に MapHubs メソッドを呼び出します。 MapHubs は、System.Web.Routing.RouteCollection クラスの拡張メソッドです。 次の例は、Global.asax ファイル内で SignalR Hubs ルートを定義する方法を示しています。

using System.Web.Routing;
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
}

SignalR 機能を ASP.NET MVC アプリケーションに追加する場合は、SignalR のルートが他のルートよりも前に追加されていることを確認します。 詳細については、「チュートリアル: SignalR と MVC 4 の概要」を参照してください。

/signalr URL

既定では、クライアントがハブに接続するために使うルート URL は "/signalr" です (この URL を、自動的に生成される JavaScript ファイル用の "/signalr/hubs" URL と混同しないでください。生成されたプロキシの詳細については、「SignalR Hubs API ガイド - JavaScript クライアント - 生成されたプロキシとその機能」を参照してください)。

特殊な状況により、このベース URL が SignalR で使用できなくなる可能性があります。たとえば、プロジェクト内に signalr というフォルダーがあり、その名前を変更したくない場合です。 その場合は、次の例に示すように、ベース URL を変更できます (サンプル コードの "/signalr" を目的の URL に置き換えます)。

URL を指定するサーバー コード

RouteTable.Routes.MapHubs("/signalr", new HubConfiguration());

URL を指定する JavaScript クライアント コード (生成されたプロキシを使用)

$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);

URL を指定する JavaScript クライアント コード (生成されたプロキシなし)

var connection = $.hubConnection("/signalr", { useDefaultPath: false });

URL を指定する .NET クライアント コード

var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);

SignalR オプションの構成

MapHubs メソッドのオーバーロードを使うと、カスタム URL、カスタム依存関係リゾルバー、次のオプションを指定できます。

  • ブラウザー クライアントからのクロスドメイン呼び出しを有効にします。

    通常、ブラウザーが http://contoso.com からページを読み込む場合、SignalR の接続は同じドメイン (http://contoso.com/signalr) で行われます。 http://contoso.com のページが http://fabrikam.com/signalr への接続を行う場合、これはクロスドメイン接続です。 セキュリティ上の理由から、クロスドメイン接続は既定では無効になっています。 詳細については、「ASP.NET SignalR Hubs API ガイド - JavaScript クライアント - クロスドメイン接続を確立する方法」を参照してください。

  • 詳細なエラー メッセージを有効にする。

    エラーが発生した場合、SignalR の既定の動作では、何が起こったのかについての詳細を含まない通知メッセージがクライアントに送信されます。 悪意のあるユーザーがアプリケーションに対する攻撃にその情報を使用できる可能性があるため、運用環境では詳細なエラー情報をクライアントに送信することはお勧めできません。 トラブルシューティングの場合、このオプションを使って、より有益なエラー レポートを一時的に有効にすることができます。

  • 自動的に生成された JavaScript プロキシ ファイルを無効にする。

    既定では、ハブ クラスのプロキシを含む JavaScript ファイルが、URL "/signalr/hubs" に応答して生成されます。 JavaScript プロキシを使いたくない場合、またはこのファイルを手動で生成してクライアント内の物理ファイルを参照したい場合は、このオプションを使ってプロキシ生成を無効にすることができます。 詳細については、「SignalR Hubs API ガイド - JavaScript クライアント - SignalR で生成されたプロキシの物理ファイルを作成する方法」を参照してください。

次の例は、MapHubs メソッドの呼び出しで SignalR 接続 URL とこれらのオプションを指定する方法を示しています。 カスタム URL を指定するには、例にある "/signalr" を使用する URL に置き換えます。

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableCrossDomain = true;
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs("/signalr", hubConfiguration);

ハブ クラスの作成と使用の方法

ハブを作成するには、Microsoft.Aspnet.Signalr.Hub から派生するクラスを作成します。 次の例は、チャット アプリケーションの単純なハブ クラスを示しています。

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}

この例では、接続されたクライアントは NewContosoChatMessage メソッドを呼び出すことができ、それを行うと、受信したデータが接続されているすべてのクライアントにブロードキャストされます。

ハブ オブジェクトの有効期間

ハブ クラスのインスタンスを作成したり、サーバー上の独自のコードからそのメソッドを呼び出したりすることはありません。これらはすべて SignalR Hubs パイプラインによって行われます。 SignalR は、クライアントがサーバーに接続、切断、またはメソッド呼び出しを行うときなど、ハブ操作を処理する必要があるたびに、ハブ クラスの新しいインスタンスを作成します。

ハブ クラスのインスタンスは一時的なものであるため、メソッド呼び出しから次のメソッド呼び出しまで状態を維持するために使うことはできません。 サーバーがクライアントからメソッド呼び出しを受け取るたびに、ハブ クラスの新しいインスタンスがメッセージを処理します。 複数の接続とメソッド呼び出しを通じて状態を維持するには、データベース、ハブ クラスの静的変数、または Hub から派生しない別のクラスなどの他の方法を使います。 ハブ クラスの静的変数などの方法を使ってデータをメモリに保持すると、アプリ ドメインのリサイクル時にデータが失われます。

ハブ クラスの外部で実行される独自のコードからクライアントにメッセージを送信する場合、ハブ クラスのインスタンスをインスタンス化してそれを行うことはできませんが、ハブ クラスの SignalR コンテキスト オブジェクトへの参照を取得することでできるようになります。 詳細については、このトピックで後述する「ハブ クラスの外部からクライアント メソッドを呼び出してグループを管理する方法」を参照してください。

JavaScript クライアントでのハブ名のキャメルケース化

既定では、JavaScript クライアントはクラス名のキャメルケース バージョンを使ってハブを参照します。 SignalR は、JavaScript コードが JavaScript 規約に準拠できるように、この変更を自動的に行います。 前の例は、JavaScript コードでは contosoChatHub として参照されます。

[サーバー]

public class ContosoChatHub : Hub

生成されたプロキシを使う JavaScript クライアント

var contosoChatHubProxy = $.connection.contosoChatHub;

クライアントでの使用に別の名前を指定する場合は、HubName 属性を追加します。 HubName 属性を使う場合、JavaScript クライアントでは名前がキャメル ケースに変更されません。

[サーバー]

[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub

生成されたプロキシを使う JavaScript クライアント

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

複数のハブ

アプリケーション内で複数のハブ クラスを定義できます。 これを行うと、接続は共有されますが、グループは別になります。

  • すべてのクライアントは同じ URL を使ってサービスとの SignalR 接続 ("/signalr"、または指定した場合はカスタム URL) を確立し、その接続はサービスによって定義されたすべてのハブに使われます。

    単一クラスですべてのハブ機能を定義する場合と比較して、複数のハブでのパフォーマンスに違いはありません。

  • すべてのハブは同じ HTTP 要求情報を取得します。

    すべてのハブは同じ接続を共有するため、サーバーが取得する HTTP 要求情報は、SignalR 接続を確立する元の HTTP 要求に含まれる情報だけです。 接続要求を使って、クエリ文字列を指定してクライアントからサーバーに情報を渡す場合、異なるハブに異なるクエリ文字列を指定することはできません。 すべてのハブは同じ情報を受け取ります。

  • 生成された JavaScript プロキシ ファイルには、すべてのハブのプロキシが 1 つのファイルに含まれます。

    JavaScript プロキシの詳細については、「SignalR Hubs API ガイド - JavaScript クライアント - 生成されたプロキシとその機能」を参照してください。

  • グループはハブ内で定義されます。

    SignalR では、接続されたクライアントのサブセットにブロードキャストする名前付きグループを定義できます。 グループはハブごとに個別に維持されます。 たとえば、"Administrators" というグループには、ContosoChatHub クラスの 1 つのクライアント セットが含まれ、同じグループ名は、StockTickerHub クラスの別のクライアント セットを参照します。

クライアントが呼び出すことができるハブ クラスのメソッドを定義する方法

クライアントから呼び出し可能にするハブ上のメソッドを公開するには、次の例に示すように、パブリック メソッドを宣言します。

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}
public class StockTickerHub : Hub
{
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
}

C# のメソッドの場合と同様に、複合型や配列など、戻り値の型とパラメーターを指定できます。 パラメーターで受信したデータ、または呼び出し元に返されたデータはすべて、JSON を使ってクライアントとサーバーの間で通信され、SignalR は複雑なオブジェクトとオブジェクトの配列のバインドを自動的に処理します。

JavaScript クライアントでのメソッド名のキャメルケース化

既定では、JavaScript クライアントは、メソッド名のキャメルケース バージョンを使ってハブ メソッドを参照します。 SignalR は、JavaScript コードが JavaScript 規約に準拠できるように、この変更を自動的に行います。

[サーバー]

public void NewContosoChatMessage(string userName, string message)

生成されたプロキシを使う JavaScript クライアント

contosoChatHubProxy.server.newContosoChatMessage($(userName, message);

クライアントでの使用に別の名前を指定する場合は、HubMethodName 属性を追加します。

[サーバー]

[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)

生成されたプロキシを使う JavaScript クライアント

contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

非同期で実行する場合

メソッドが実行時間の長い場合、またはデータベース検索や Web サービス呼び出しなどの待機を伴う作業を実行する必要がある場合は、Task (戻り値 void の代わり) または Task<T> オブジェクト (戻り値型 T の代わり) を返すことにより、ハブ メソッドを非同期にします。 メソッドから Task オブジェクトを返すと、SignalR は Task が完了するのを待ってから、ラップされていない結果をクライアントに送り返すため、クライアントでメソッド呼び出しをコーディングする方法に違いはありません。

ハブ メソッドを非同期にすると、WebSocket トランスポートを使うときに接続がブロックされるのを回避できます。 ハブ メソッドが同期的に実行され、トランスポートが WebSocket である場合、同じクライアントからのハブ上のメソッドの後続の呼び出しは、ハブ メソッドが完了するまでブロックされます。

次の例は、同期または非同期で実行するようにコード化された同じメソッドと、その後にどちらのバージョンの呼び出しにも機能する JavaScript クライアント コードを示しています。

同期

public IEnumerable<Stock> GetAllStocks()
{
    // Returns data from memory.
    return _stockTicker.GetAllStocks();
}

非同期 - ASP.NET 4.5

public async Task<IEnumerable<Stock>> GetAllStocks()
{
    // Returns data from a web service.
    var uri = Util.getServiceUri("Stocks");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
    }
}

生成されたプロキシを使う JavaScript クライアント

stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
    $.each(stocks, function () {
        alert(this.Symbol + ' ' + this.Price);
    });
});

ASP.NET 4.5 での非同期メソッドの使用方法の詳細については、「ASP.NET MVC 4 での非同期メソッドの使用」を参照してください。

オーバーロードの定義

メソッドのオーバーロードを定義する場合は、各オーバーロードのパラメーターの数が異なる必要があります。 異なるパラメーターの種類を指定するだけでオーバーロードを区別する場合、ハブ クラスはコンパイルされますが、クライアントがオーバーロードの 1 つを呼び出そうとすると、実行時に SignalR サービスが例外をスローします。

ハブ クラスからクライアント メソッドを呼び出す方法

サーバーからクライアント メソッドを呼び出すには、ハブ クラスのメソッドで Clients プロパティを使います。 次の例は、接続されているすべてのクライアントで addNewMessageToPage を呼び出すサーバー コードと、JavaScript クライアントでメソッドを定義するクライアント コードを示しています。

[サーバー]

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}

生成されたプロキシを使う JavaScript クライアント

contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + htmlEncode(name)
        + '</strong>: ' + htmlEncode(message) + '<li>');
};

クライアント メソッドから戻り値を取得することはできません。int x = Clients.All.add(1,1) などの構文は機能しません。

パラメーターには複合型と配列を指定できます。 次の例では、メソッド パラメーターで複合型をクライアントに渡します。

複合オブジェクトを使ってクライアント メソッドを呼び出すサーバー コード

public void SendMessage(string name, string message)
{
    Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}

複合オブジェクトを定義するサーバー コード

public class ContosoChatMessage
{
    public string UserName { get; set; }
    public string Message { get; set; }
}

生成されたプロキシを使う JavaScript クライアント

var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
    console.log(message.UserName + ' ' + message.Message);
});

RPC を受信するクライアントの選択

クライアント プロパティは、RPC を受信するクライアントを指定するためのいくつかのオプションを提供する HubConnectionContext オブジェクトを返します。

  • 接続されたすべてのクライアント。

    Clients.All.addContosoChatMessageToPage(name, message);
    
  • 呼び出し元のクライアントのみ。

    Clients.Caller.addContosoChatMessageToPage(name, message);
    
  • 呼び出し元クライアントを除くすべてのクライアント。

    Clients.Others.addContosoChatMessageToPage(name, message);
    
  • 接続 ID によって識別される特定のクライアント。

    Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
    

    この例では、呼び出し元クライアントで addContosoChatMessageToPage を呼び出し、Clients.Caller を使う場合と同じ効果があります。

  • 接続 ID で識別される、指定されたクライアントを除く接続されているすべてのクライアント。

    Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • 指定されたグループ内の接続されているすべてのクライアント。

    Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • 接続 ID によって識別される、指定されたクライアントを除く、指定されたグループ内のすべての接続されたクライアント。

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • 呼び出し元クライアントを除く、指定されたグループ内の接続されているすべてのクライアント。

    Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);
    

メソッド名のコンパイル時の検証がない

指定したメソッド名は動的オブジェクトとして解釈されます。つまり、IntelliSense とコンパイル時の検証がないことを意味します。 式は実行時に評価されます。 メソッド呼び出しが実行されると、SignalR はメソッド名とパラメーター値をクライアントに送信します。クライアントにその名前と一致するメソッドがある場合、そのメソッドが呼び出され、パラメーター値がクライアントに渡されます。 クライアントで一致するメソッドが見つからない場合、エラーは発生しません。 クライアント メソッドを呼び出すときに SignalR がバックグラウンドでクライアントに送信するデータの形式については、「SignalR の概要」を参照してください。

大文字と小文字を区別しないメソッド名の照合

メソッド名の照合では大文字と小文字が区別されません。 たとえば、サーバー上の Clients.All.addContosoChatMessageToPage はクライアント上で AddContosoChatMessageToPageaddcontosochatmessagetopage、または addContosoChatMessageToPage を実行します。

非同期実行

呼び出すメソッドは非同期で実行されます。 クライアントへのメソッド呼び出しの後のコードは、後続のコード行でメソッドの完了を待機するように指定しない限り、SignalR がクライアントへのデータ送信を完了するのを待たずにすぐに実行されます。 次のコード例は、2 つのクライアント メソッドを順番に実行する方法を示しています。1 つは .NET 4.5 で動作するコードを使用し、1 つは .NET 4 で動作するコードを使用しています。

.NET 4.5 の例

async public Task NewContosoChatMessage(string name, string message)
{
    await Clients.Others.addContosoChatMessageToPage(data);
    Clients.Caller.notifyMessageSent();
}

.NET 4 の例

public void NewContosoChatMessage(string name, string message)
{
    (Clients.Others.addContosoChatMessageToPage(data) as Task).ContinueWith(antecedent =>
        Clients.Caller.notifyMessageSent());
}

クライアント メソッドが終了するまで待機してから次のコード行を実行するために await または ContinueWith を使用する場合、実際には次のコード行を実行する前にクライアントがメッセージを受信するわけではありません。 クライアント メソッド呼び出しの "完了" とは、SignalR がメッセージの送信に必要なすべての作業を完了したことを意味するだけです。 クライアントがメッセージを受信したことを確認する必要がある場合は、そのメカニズムを自分でプログラムする必要があります。 たとえば、ハブで MessageReceived メソッドをコーディングし、クライアントで必要な作業を行った後にクライアントの addContosoChatMessageToPage メソッドで MessageReceived を呼び出すことができます。 ハブの MessageReceived では、実際のクライアントの受信と元のメソッド呼び出しの処理に依存する任意の作業を行うことができます。

文字列変数をメソッド名として使う方法

文字列変数をメソッド名として使ってクライアント メソッドを呼び出す場合は、Clients.All (または Clients.OthersClients.Caller など) を IClientProxy にキャストしてから、Invoke (methodName、引数...) を呼び出します。

public void NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    proxy.Invoke(methodToCall, name, message);
}

ハブ クラスからグループ メンバーシップを管理する方法

SignalR のグループは、接続されたクライアントの指定されたサブセットにメッセージをブロードキャストする方法を提供します。 グループには任意の数のクライアントを含めることができ、クライアントは任意の数のグループのメンバーにすることができます。

グループ メンバーシップを管理するには、ハブ クラスの Groups プロパティによって提供される Add メソッドと Remove メソッドを使います。 次の例は、クライアント コードによって呼び出されるハブ メソッドで使われる Groups.Add メソッドと Groups.Remove メソッド、その後にそれらを呼び出す JavaScript クライアント コードを示しています。

[サーバー]

public class ContosoChatHub : Hub
{
    public Task JoinGroup(string groupName)
    {
        return Groups.Add(Context.ConnectionId, groupName);
    }

    public Task LeaveGroup(string groupName)
    {
        return Groups.Remove(Context.ConnectionId, groupName);
    }
}

生成されたプロキシを使う JavaScript クライアント

contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);

グループを明示的に作成する必要はありません。 実際には、グループは、Groups.Add への呼び出しで初めて名前を指定したときに自動的に作成され、最後の接続をそのメンバーシップから削除すると削除されます。

グループ メンバーシップ一覧またはグループの一覧を取得するための API はありません。 SignalR は、pub/sub モデルに基づいてクライアントとグループにメッセージを送信します。サーバーはグループやグループ メンバーシップの一覧を維持しません。 Web ファームにノードを追加するたびに、SignalR が維持する状態を新しいノードに伝達する必要があるため、これは、スケーラビリティを最大化するのに役立ちます。

Add および Remove メソッドの非同期実行

Groups.Add メソッドと Groups.Remove メソッドは非同期で実行されます。 クライアントをグループに追加し、そのグループを使ってクライアントにメッセージをすぐに送信する場合は、最初に Groups.Add メソッドが完了していることを確認する必要があります。 次のコード例は、これを行う方法を示しています。1 つは .NET 4.5 で動作するコードを使用し、1 つは .NET 4 で動作するコードを使用しています

.NET 4.5 の例

async public Task JoinGroup(string groupName)
{
    await Groups.Add(Context.ConnectionId, groupName);
    Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}

.NET 4 の例

public void JoinGroup(string groupName)
{
    (Groups.Add(Context.ConnectionId, groupName) as Task).ContinueWith(antecedent =>
        Clients.Group(groupName).addContosoChatMessageToPage(Context.ConnectionId + " added to group"));
}

グループ メンバーシップの永続性

SignalR はユーザーではなく接続を追跡するため、ユーザーが接続を確立するたびにユーザーを同じグループに含める場合は、ユーザーが新しい接続を確立するたびに Groups.Add を呼び出す必要があります。

一時的に接続が失われた後、SignalR が自動的に接続を復元できる場合があります。 この場合、SignalR は新しい接続を確立するのではなく、同じ接続を復元するため、クライアントのグループ メンバーシップが自動的に復元されます。 これは、グループ メンバーシップを含む各クライアントの接続状態がクライアントにラウンド トリップされるため、一時的な中断がサーバーの再起動または失敗の結果である場合でも可能です。 サーバーがダウンし、接続がタイム アウトになる前に新しいサーバーに置き換えられた場合、クライアントは新しいサーバーに自動的に再接続し、メンバーとなっているグループに再登録できます。

接続が失われた後、接続がタイム アウトになったとき、またはクライアントが切断されたとき (ブラウザーが新しいページに移動した場合など)、接続が自動的に復元できない場合、グループ メンバーシップは失われます。 次回、ユーザーが接続すると、新しい接続になります。 同じユーザーが新しい接続を確立したときにグループ メンバーシップを維持するには、アプリケーションでユーザーとグループ間の関連付けを追跡し、ユーザーが新しい接続を確立するたびにグループ メンバーシップを復元する必要があります。

接続と再接続の詳細については、このトピックで後述する「ハブ クラスで接続の有効期間イベントを処理する方法」を参照してください。

単一ユーザー グループ

SignalR を使うアプリケーションは、通常、どのユーザーがメッセージを送信したか、どのユーザーがメッセージを受信すべきかを知るために、ユーザーと接続の間の関連付けを追跡する必要があります。 グループは、これを行うために一般的に使われる 2 つのパターンのいずれかで使われます。

  • 単一ユーザー グループ。

    ユーザー名をグループ名として指定し、ユーザーが接続または再接続するたびに現在の接続 ID をグループに追加できます。 ユーザーにメッセージを送信するには、グループに送信します。 この方法の欠点は、ユーザーがオンラインかオフラインかを確認する方法がグループには用意されていないことです。

  • ユーザー名と接続 ID 間の関連付けを追跡する。

    各ユーザー名と 1 つ以上の接続 ID との関連付けを辞書またはデータベースに格納し、ユーザーが接続または切断するたびに格納されたデータを更新できます。 ユーザーにメッセージを送信するには、接続 ID を指定します。 この方法の欠点は、より多くのメモリを必要とすることです。

ハブ クラスで接続の有効期間イベントを処理する方法

接続の有効期間イベントを処理する一般的な理由は、ユーザーが接続されているかどうかを追跡し、ユーザー名と接続 ID の関連付けを追跡することです。 クライアントの接続または切断時に独自のコードを実行するには、次の例に示すように、ハブ クラスの OnConnectedOnDisconnectedOnReconnected 仮想メソッドをオーバーライドします。

public class ContosoChatHub : Hub
{
    public override Task OnConnected()
    {
        // Add your own code here.
        // For example: in a chat application, record the association between
        // the current connection ID and user name, and mark the user as online.
        // After the code in this method completes, the client is informed that
        // the connection is established; for example, in a JavaScript client,
        // the start().done callback is executed.
        return base.OnConnected();
    }

    public override Task OnDisconnected()
    {
        // Add your own code here.
        // For example: in a chat application, mark the user as offline, 
        // delete the association between the current connection id and user name.
        return base.OnDisconnected();
    }

    public override Task OnReconnected()
    {
        // Add your own code here.
        // For example: in a chat application, you might have marked the
        // user as offline after a period of inactivity; in that case 
        // mark the user as online again.
        return base.OnReconnected();
    }
}

OnConnected、OnDisconnected、OnReconnected が呼び出されるとき

ブラウザーが新しいページに移動するたびに、新しい接続を確立する必要があります。つまり、SignalR は OnDisconnected メソッドを、続いて OnConnected メソッドを実行します。 SignalR は、新しい接続が確立されると常に新しい接続 ID を作成します。

OnReconnected メソッドは、ケーブルが一時的に取り外され、接続がタイム アウトになる前に再接続された場合など、SignalR が自動的に回復できる接続が一時的に切断されたときに呼び出されます。OnDisconnected メソッドは、ブラウザーが新しいページに移動したときなど、クライアントが切断され、SignalR が自動的に再接続できないときに呼び出されます。 そのため、所与のクライアントで考えられるイベントのシーケンスは、OnConnectedOnReconnectedOnDisconnected か、あるいは OnConnectedOnDisconnected です。 所与の接続に対し、シーケンス OnConnectedOnDisconnectedOnReconnected は起こりません。

OnDisconnected メソッドは、サーバーがダウンした場合やアプリ ドメインがリサイクルされた場合など、一部のシナリオでは呼び出されません。 別のサーバーがオンラインになるか、アプリ ドメインがリサイクルを完了すると、一部のクライアントが再接続して OnReconnected イベントを発生させる可能性があります。

詳細については、「SignalR の接続の有効期間イベントの概要と処理」を参照してください。

呼び出し元の状態が設定されない

接続の有効期間イベント ハンドラー メソッドはサーバーから呼び出されます。つまり、クライアント上の state オブジェクトに設定した状態は、サーバー上の Caller プロパティには設定されません。 state オブジェクトと Caller プロパティの詳細については、このトピックで後述する「クライアントとハブ クラスの間で状態を渡す方法」を参照してください。

Context プロパティからクライアントに関する情報を取得する方法

クライアントに関する情報を取得するには、ハブ クラスの Context プロパティを使います。 Context プロパティは、以下の情報へのアクセスを提供する HubCallerContext オブジェクトを返します。

  • 呼び出し元クライアントの接続 ID。

    string connectionID = Context.ConnectionId;
    

    接続 ID は、SignalR によって割り当てられる GUID です (独自のコードで値を指定することはできません)。 接続ごとに 1 つの接続 ID があり、アプリケーションに複数のハブがある場合は、すべてのハブで同じ接続 ID が使われます。

  • HTTP ヘッダー データ。

    System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
    

    Context.Headers から HTTP ヘッダーを取得することもできます。 同じものへの参照が複数ある理由は、Context.Headers が最初に作成され、Context.Request プロパティが後で追加され、Context.Headers が下位互換性のために保持されたためです。

  • クエリ文字列データ。

    System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString;
    string parameterValue = queryString["parametername"]
    

    Context.QueryString からクエリ文字列データを取得することもできます。

    このプロパティで取得するクエリ文字列は、SignalR 接続を確立した HTTP 要求で使われたものです。 接続を構成することでクライアントにクエリ文字列パラメーターを追加できます。これは、クライアントに関するデータをクライアントからサーバーに渡す便利な方法です。 次の例は、生成されたプロキシを使うときに JavaScript クライアントにクエリ文字列を追加する 1 つの方法を示しています。

    $.connection.hub.qs = { "version" : "1.0" };
    

    クエリ文字列パラメーターの設定の詳細については、JavaScript.NET クライアントの API ガイドを参照してください。

    接続に使われるトランスポート メソッドは、SignalR によって内部的に使われる他の値と共に、クエリ文字列データで確認できます。

    string transportMethod = queryString["transport"];
    

    transportMethod の値は、"webSockets"、"serverSentEvents"、"foreverFrame"、または "longPolling" になります。 OnConnected イベント ハンドラー メソッドでこの値を確認すると、シナリオによっては、接続に対して最終的にネゴシエートされたトランスポート メソッドではないトランスポート値が最初に取得される場合があることに注意してください。 その場合、メソッドは例外をスローし、後で最終的なトランスポート メソッドが確立されたときにもう一度呼び出されます。

  • Cookie。

    System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
    

    Context.RequestCookies から Cookie を取得することもできます。

  • ユーザー情報。

    System.Security.Principal.IPrincipal user = Context.User;
    
  • 要求の HttpContext オブジェクト。

    System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
    

    SignalR 接続の HttpContext オブジェクトを取得するには、HttpContext.Current を取得する代わりにこのメソッドを使います。

クライアントとハブ クラスの間で状態を渡す方法

クライアント プロキシは、メソッド呼び出しごとにサーバーに送信するデータを格納できる state オブジェクトを提供します。 サーバーでは、クライアントによって呼び出されるハブ メソッドの Clients.Caller プロパティでこのデータにアクセスできます。 Clients.Caller プロパティは、接続の有効期間イベント ハンドラー メソッド OnConnectedOnDisconnectedOnReconnected には設定されません。

state オブジェクトと Clients.Caller プロパティ内のデータの作成または更新は、双方向に機能します。 サーバー内の値を更新して、それらをクライアントに渡すことができます。

次の例は、メソッド呼び出しごとにサーバーに送信する状態を格納する JavaScript クライアント コードを示しています。

contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";

次の例は、.NET クライアントでの同等のコードを示しています。

contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";

ハブ クラスでは、Clients.Caller プロパティでこのデータにアクセスできます。 次の例は、前の例で参照した状態を取得するコードを示しています。

public void NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}

Note

状態を永続化するためのこのメカニズムは、state または Clients.Caller プロパティに入力したものすべてがメソッド呼び出しごとにラウンド トリップされるため、大量のデータを対象としたものではありません。 これは、ユーザー名やカウンターなどの小さな項目に便利です。

ハブ クラスでのエラーの処理方法

Hub クラス メソッド内で発生するエラーを処理するには、次のメソッドのいずれかまたは両方を使用します。

  • メソッド コードを try-catch ブロックでラップし、例外オブジェクトをログします。 デバッグ目的ではクライアントに例外を送信できますが、セキュリティ上の理由から、運用環境のクライアントに詳細情報を送信することはお勧めできません。

  • OnIncomingError メソッドを処理する Hubs パイプライン モジュールを作成します。 次の例は、エラーをログに記録するパイプライン モジュールと、その後にそのモジュールを Hubs パイプラインの中に挿入する Global.asax のコードを示しています。

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(Exception ex, IHubIncomingInvokerContext context)
        {
            Debug.WriteLine("=> Exception " + ex.Message);
            if (ex.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + ex.InnerException.Message);
            }
            base.OnIncomingError(ex, context);
        }
    }
    
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        RouteTable.Routes.MapHubs();
    }
    

ハブ パイプライン モジュールの詳細については、このトピックで後述する「Hubs パイプラインをカスタマイズする方法」を参照してください。

トレースを有効にする方法

サーバー側のトレースを有効にするには、次の例に示すように、Web.config ファイルに system.diagnostics 要素を追加します。

<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit https://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="SignalRSamples" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;" />
    <add name="SignalRSamplesWithMARS" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;MultipleActiveResultSets=true;" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <system.diagnostics>
    <sources>
      <source name="SignalR.SqlMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
     <source name="SignalR.ServiceBusMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
     </source>
     <source name="SignalR.ScaleoutMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
      </source>
      <source name="SignalR.Transports.WebSocketTransport">
        <listeners>
          <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.ServerSentEventsTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.ForeverFrameTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.LongPollingTransport">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.TransportHeartBeat">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SignalRSwitch" value="Verbose" />
    </switches>
    <sharedListeners>
      <add name="SignalR-Transports" 
           type="System.Diagnostics.TextWriterTraceListener" 
           initializeData="transports.log.txt" />
        <add name="SignalR-Bus"
           type="System.Diagnostics.TextWriterTraceListener"
           initializeData="bus.log.txt" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
  </entityFramework>
</configuration>

Visual Studio でアプリケーションを実行すると、[出力] ウィンドウでログを表示できます。

ハブ クラスの外部からクライアント メソッドを呼び出してグループを管理する方法

ハブ クラスとは異なるクラスからクライアント メソッドを呼び出すには、ハブの SignalR コンテキスト オブジェクトへの参照を取得し、それを使ってクライアント上のメソッドを呼び出すか、グループを管理します。

次のサンプル StockTicker クラスは、コンテキスト オブジェクトを取得し、それをクラスのインスタンスに格納し、そのクラス インスタンスを静的プロパティに格納し、シングルトン クラス インスタンスからのコンテキストを使って、StockTickerHub という名前のハブに接続されているクライアントの updateStockPrice メソッドを呼び出します。

// For the complete example, go to 
// http://www.asp.net/signalr/overview/signalr-1x/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
    // Singleton instance
    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
        () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));

    private IHubContext _context;

    private StockTicker(IHubContext context)
    {
        _context = context;
    }

    // This method is invoked by a Timer object.
    private void UpdateStockPrices(object state)
    {
        foreach (var stock in _stocks.Values)
        {
            if (TryUpdateStockPrice(stock))
            {
                _context.Clients.All.updateStockPrice(stock);
            }
        }
    }

存続期間の長いオブジェクトでコンテキストを複数回使う必要がある場合は、参照を毎回取得するのではなく、1 回取得して保存します。 コンテキストを一度取得すると、ハブ メソッドがクライアント メソッドを呼び出すのと同じ順序で、SignalR がクライアントにメッセージを送信するようになります。 ハブの SignalR コンテキストの使用方法を示すチュートリアルについては、ASP.NET SignalR を使ったサーバー ブロードキャストに関するページを参照してください。

クライアント メソッドの呼び出し

RPC を受信するクライアントを指定できますが、ハブ クラスから呼び出す場合よりもオプションが少なくなります。 その理由は、コンテキストがクライアントからの特定の呼び出しに関連付けられていないため、現在の接続 ID の知識を必要とするメソッド (Clients.OthersClients.Caller、または Clients.OthersInGroup など) が使用できないためです。 次のオプションを選択できます。

  • 接続されたすべてのクライアント。

    context.Clients.All.addContosoChatMessageToPage(name, message);
    
  • 接続 ID によって識別される特定のクライアント。

    context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
    
  • 接続 ID で識別される、指定されたクライアントを除く接続されているすべてのクライアント。

    context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • 指定されたグループ内の接続されているすべてのクライアント。

    context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • 接続 ID で識別される、指定されたクライアントを除く、指定されたグループ内のすべての接続されたクライアント。

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    

ハブ クラスのメソッドから非ハブ クラスを呼び出す場合は、現在の接続 ID を渡し、それを Clients.ClientClients.AllExcept、または Clients.Group と共に使って、Clients.CallerClients.Others、または Clients.OthersInGroup をシミュレートできます。 次の例では、Broadcaster クラスが Clients.Others をシミュレートできるように、MoveShapeHub クラスが接続 ID を Broadcaster クラスに渡します。

// For the complete example, see
// http://www.asp.net/signalr/overview/getting-started/tutorial-high-frequency-realtime-with-signalr
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
{
    // Code not shown puts a singleton instance of Broadcaster in this variable.
    private Broadcaster _broadcaster;

    public void UpdateModel(ShapeModel clientModel)
    {
        clientModel.LastUpdatedBy = Context.ConnectionId;
        // Update the shape model within our broadcaster
        _broadcaster.UpdateShape(clientModel);
    }
}

public class Broadcaster
{
    public Broadcaster()
    {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    }

    public void UpdateShape(ShapeModel clientModel)
    {
        _model = clientModel;
        _modelUpdated = true;
    }

    // Called by a Timer object.
    public void BroadcastShape(object state)
    {
        if (_modelUpdated)
        {
            _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
            _modelUpdated = false;
        }
    }
}

グループ メンバーシップの管理

グループを管理する場合は、ハブ クラスで行うのと同じオプションがあります。

  • クライアントをグループに追加する

    context.Groups.Add(connectionID, groupName);
    
  • クライアントをグループから削除する

    context.Groups.Remove(connectionID, groupName);
    

Hubs パイプラインをカスタマイズする方法

SignalR を使うと、独自のコードをハブ パイプラインに挿入できます。 次の例は、クライアントから受けとった受信メソッド呼び出しと、クライアントで呼び出される送信メソッド呼び出しをそれぞれログするカスタム ハブ パイプライン モジュールを示しています。

public class LoggingPipelineModule : HubPipelineModule 
{ 
    protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context) 
    { 
        Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name); 
        return base.OnBeforeIncoming(context); 
    }   
    protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context) 
    { 
        Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); 
        return base.OnBeforeOutgoing(context); 
    } 
}

Global.asax ファイル内の次のコードは、ハブ パイプライン内で実行するモジュールを登録します。

protected void Application_Start(object sender, EventArgs e) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    RouteTable.Routes.MapHubs();
}

オーバーライドできるさまざまなメソッドがあります。 完全な一覧については、「HubPipelineModule メソッド」を参照してください。