ASP.NET SignalR Hub API ガイド-サーバー (C#)

FletcherTom Dykstra

Warning

このドキュメントは、SignalR の最新バージョンではありません。 ASP.NET Core SignalRをご覧ください。

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

SignalR Hub API を使用すると、サーバーから接続されたクライアントおよびクライアントからサーバーへのリモートプロシージャコール (Rpc) を行うことができます。 サーバーコードでは、クライアントから呼び出すことができるメソッドを定義し、クライアントで実行するメソッドを呼び出すことができます。 クライアントコードでは、サーバーから呼び出すことができるメソッドを定義し、サーバーで実行されるメソッドを呼び出すことができます。 SignalR は、クライアントとサーバーのすべての組み込みを処理します。

SignalR には、永続的な接続と呼ばれる下位レベルの API も用意されています。 SignalR、ハブ、および永続的な接続の概要については、「 SignalR 2 の概要」を参照してください。

このトピックで使用されているソフトウェアのバージョン

トピックのバージョン

以前のバージョンの SignalR の詳細については、「 古いバージョンの SignalR」を参照してください。

質問とコメント

このチュートリアルの気に入った点と、ページの下部にあるコメントで改善できることについて、フィードバックをお寄せください。 チュートリアルに直接関係のない質問がある場合は、 ASP.NET SignalR フォーラム または StackOverflow.comに投稿できます。

概要

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

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

SignalR 2 のサーバーコンポーネントは、.NET 4.5 でのみ使用できます。 .NET 4.0 を実行しているサーバーでは、SignalR v1. x を使用する必要があります。

SignalR ミドルウェアを登録する方法

クライアントがハブへの接続に使用するルートを定義するには、 MapSignalR アプリケーションの起動時にメソッドを呼び出します。 MapSignalR は、クラスの 拡張メソッド です OwinExtensions 。 次の例は、OWIN startup クラスを使用して SignalR Hub ルートを定義する方法を示しています。

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }

    }
}

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

/Signalr URL

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

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

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

app.MapSignalR("/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 オプションの構成

メソッドのオーバーロードを MapSignalR 使用すると、カスタム URL、カスタム依存関係競合回避モジュール、および次のオプションを指定できます。

  • ブラウザークライアントから CORS または JSONP を使用してクロスドメイン呼び出しを有効にします。

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

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

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

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

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

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

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);

ハブクラスを作成して使用する方法

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

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

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

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

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

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

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

JavaScript クライアントでのハブ名の camel 形式

既定では、JavaScript クライアントは、camel 形式のクラス名を使用してハブを参照します。 SignalR は、JavaScript コードが JavaScript の規則に準拠できるように、この変更を自動的に行います。 前の例は、JavaScript コードではと呼ばれて contosoChatHub います。

[サーバー]

public class ContosoChatHub : Hub

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

var contosoChatHubProxy = $.connection.contosoChatHub;

クライアントに使用する別の名前を指定する場合は、属性を追加し HubName ます。 属性を使用する場合 HubName 、JavaScript クライアントでの camel 形式の変更はありません。

[サーバー]

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

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

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

複数のハブ

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

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

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

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

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

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

    JavaScript プロキシの詳細については、「 SignalR HUB API Guide-Javascript Client-生成されたプロキシとその機能」を参照してください。

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

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

Strongly-Typed ハブ

クライアントが参照できる (そして、ハブメソッドで Intellisense を有効にする) ハブメソッドのインターフェイスを定義するには、次のようにで Hub<T> はなく (SignalR 2.1 で導入された) からハブを派生させ Hub ます。

public class StrongHub : Hub<IClient>
{
    public async Task Send(string message)
    {
        await Clients.All.NewMessage(message);
    }
}

public interface IClient
{
    Task NewMessage(string message);
}

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

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

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

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

JavaScript クライアントでのメソッド名の camel 形式の表記

既定では、JavaScript クライアントは、camel 形式のメソッド名を使用してハブメソッドを参照します。 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 サービスの呼び出しなどの待機が必要な処理を実行する必要がある場合は、タスク( void 戻り値の型の代わり) またはタスク < T > オブジェクト (戻り値の型の代わり) を返すことによって、ハブメソッドを非同期にし T ます。 メソッドからオブジェクトを返すと、 Task SignalR はが完了するまで待機してから、ラップされてい Task ない結果をクライアントに返します。したがって、クライアントでメソッド呼び出しをコーディングする方法に違いはありません。

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

次の例は、同期的または非同期的に実行するようにコード化された同じメソッドと、いずれかのバージョンの呼び出しに使用できる JavaScript クライアントコードを示しています。

Synchronous

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

非同期

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 での非同期メソッドの使用」を参照してください。

オーバーロードの定義

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

ハブメソッド呼び出しからの進行状況の報告

SignalR 2.1 では、.NET 4.5 で導入された 進行状況レポートパターン のサポートが追加されています。 進行状況レポートを実装するには、 IProgress<T> クライアントがアクセスできるハブメソッドのパラメーターを定義します。

public class ProgressHub : Hub
{
    public async Task<string> DoLongRunningThing(IProgress<int> progress)
    {
        for (int i = 0; i <= 100; i+=5)
        {
            await Task.Delay(200);
            progress.Report(i);
        }
        return "Job complete!";
    }
}

実行時間の長いサーバーメソッドを作成する場合は、ハブスレッドをブロックするのではなく、Async/Await のような非同期プログラミングパターンを使用することが重要です。

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

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

[サーバー]

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

クライアントメソッドの呼び出しは非同期操作であり、を返し Task ます。 await を使用して次のことを行います。

  • メッセージがエラーなしで送信されるようにします。
  • Try-catch ブロックのエラーをキャッチして処理できるようにする場合は。

生成されたプロキシを使用する 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 async Task SendMessage(string name, string message)
{
    await 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 を受信するクライアントを選択する

Clients プロパティは、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);
    
  • UserId によって識別される特定のユーザー。

    Clients.User(userid).addContosoChatMessageToPage(name, message);
    

    既定では、これはですが、 IPrincipal.Identity.Name IUserIdProvider の実装をグローバルホストに登録することによって変更できます。

  • 接続 Id の一覧にあるすべてのクライアントとグループ。

    Clients.Clients(ConnectionIds).broadcastMessage(name, message);
    
  • グループの一覧。

    Clients.Groups(GroupIds).broadcastMessage(name, message);
    
  • ユーザー名で指定します。

    Clients.Client(username).broadcastMessage(name, message);
    
  • SignalR 2.1 で導入されたユーザー名の一覧です。

    Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
    

メソッド名に対するコンパイル時の検証がありません

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

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

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

非同期実行

呼び出すメソッドは、非同期的に実行されます。 クライアントへのメソッド呼び出しの後に来るコードは、SignalR がクライアントへのデータの転送を完了するまで待たずにすぐに実行されます。ただし、後続のコード行がメソッドの完了を待機するように指定した場合は除きます。 次のコード例は、2つのクライアントメソッドを順番に実行する方法を示しています。

Await の使用 (.NET 4.5)

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

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

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

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

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

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

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

グループメンバーシップを管理するには、ハブクラスのプロパティによって提供される Add メソッドと Remove メソッドを使用し Groups ます。 次の例は、 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 ます。 その方法を次のコード例に示します。

クライアントをグループに追加し、そのクライアントにメッセージングを行う

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

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

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

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

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

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

シングルユーザーグループ

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

  • シングルユーザーグループ。

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

  • ユーザー名と接続 Id の間の関連付けを追跡します。

    各ユーザー名と1つまたは複数の接続 Id の間の関連付けをディクショナリまたはデータベースに格納し、ユーザーが接続または切断するたびに格納されているデータを更新することができます。 ユーザーにメッセージを送信するには、接続 Id を指定します。 この方法の欠点は、より多くのメモリを必要とすることです。

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

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

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(bool stopCalled)
    {
        // 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(stopCalled);
    }

    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、Onconnected、および Onconnected が呼び出されたとき

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

接続が一時的に切断され、接続がタイムアウトする前にケーブルが一時的に切断されて再接続された OnReconnected 場合など、接続が一時的に切断されたときに、メソッドが呼び出されます。 OnDisconnected メソッドは、クライアントが切断され、SignalR が自動的に再接続できない場合 (ブラウザーが新しいページに移動した場合など) に呼び出されます。 したがって、特定のクライアントに対して可能な一連のイベントは、、、 OnConnected OnReconnected OnDisconnected 、または OnConnected OnDisconnected です。 OnConnected OnDisconnected OnReconnected 特定の接続については、シーケンスは表示されません。

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

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

呼び出し元の状態が設定されていません

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

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

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

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

    string connectionID = Context.ConnectionId;
    

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

  • HTTP ヘッダーデータ。

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

    また、から HTTP ヘッダーを取得することもでき Context.Headers ます。 同じことに対する複数の参照がある理由は、 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 "websocket"、"Serverpolybase events"、"" "および" longPolling "になります。 イベントハンドラーメソッドでこの値を確認した場合 OnConnected 、一部のシナリオでは、最初に接続に対してネゴシエートされた最後のトランスポートメソッドではないトランスポート値が取得される可能性があることに注意してください。 その場合、メソッドは例外をスローし、最後のトランスポートメソッドが確立されると、後で再度呼び出されます。

  • Cookie.

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

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

  • ユーザー情報。

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

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

    HttpContext.CurrentSignalR 接続のオブジェクトを取得するのではなく、このメソッドを使用し HttpContext ます。

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

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

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

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

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

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

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

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

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

Note

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

VB.NET または厳密に型指定されたハブでは、を介して呼び出し元の状態オブジェクトにアクセスすることはできません Clients.Caller 。代わりに、 Clients.CallerState (SignalR 2.1 で導入) を使用します。

C での CallerState の使用#

public async Task NewContosoChatMessage(string data)
{
    string userName = Clients.CallerState.userName;
    string computerName = Clients.CallerState.computerName;
    await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
}

Visual Basic での CallerState の使用

Public Async Function NewContosoChatMessage(message As String) As Task
    Dim userName As String = Clients.CallerState.userName
    Dim computerName As String = Clients.CallerState.computerName
    Await Clients.Others.addContosoChatMessageToPage(message, userName, computerName)
End Sub

ハブクラスでエラーを処理する方法

ハブクラスのメソッドで発生したエラーを処理するには、まず、を使用して、非同期操作 (クライアントメソッドの呼び出しなど) の例外を "観察" し await ます。 その後、次の1つまたは複数の方法を使用します。

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

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

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
        {
            Debug.WriteLine("=> Exception " + exceptionContext.Error.Message);
            if (exceptionContext.Error.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
            }
            base.OnIncomingError(exceptionContext, invokerContext);
    
        }
    }
    
    public void Configuration(IAppBuilder app)
    {
        // Any connection or hub wire up and configuration should go here
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        app.MapSignalR();
    }
    
  • HubException(SignalR 2 で導入された) クラスを使用します。 このエラーは、任意のハブ呼び出しからスローされる可能性があります。 コンストラクターは、 HubError 文字列メッセージと、追加のエラーデータを格納するためのオブジェクトを受け取ります。 SignalR は、例外を自動的にシリアル化してクライアントに送信します。このとき、ハブメソッドの呼び出しを拒否または失敗するために使用されます。

    次のコードサンプルは、ハブの呼び出し中にをスローする方法 HubException と、JavaScript および .net クライアントで例外を処理する方法を示しています。

    HubException クラスをデモンストレーションするサーバーコード

    public class MyHub : Hub
    {
        public async Task Send(string message)
        {
            if(message.Contains("<script>"))
            {
                throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
            }
    
            await Clients.All.send(message);
        }
    }
    

    ハブで HubException をスローするための応答を示す JavaScript クライアントコード

    myHub.server.send("<script>")
                .fail(function (e) {
                    if (e.source === 'HubException') {
                        console.log(e.message + ' : ' + e.data.user);
                    }
                });
    

    ハブで HubException をスローするための応答を示す .NET クライアントコード

    try
    {
        await myHub.Invoke("Send", "<script>");
    }
    catch(HubException ex)
    {
        Console.WriteLine(ex.Message);
    }
    

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

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

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

<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 context オブジェクトへの参照を取得し、それを使用してクライアント上のメソッドを呼び出すか、グループを管理します。

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

// For the complete example, go to 
// http://www.asp.net/signalr/overview/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);
            }
        }
    }

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

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

どのクライアントが RPC を受信するかを指定できますが、ハブクラスからを呼び出す場合よりもオプションが少なくて済みます。 その理由は、コンテキストがクライアントからの特定の呼び出しに関連付けられていないため、、、またはなど、現在の接続 ID に関する情報を必要とするメソッド Clients.Others Clients.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.Client Clients.AllExcept Clients.Group Clients.Caller Clients.Others Clients.OthersInGroup ます。 次の例では、クラスは接続 ID をクラスに渡して、クラスがを MoveShapeHub Broadcaster シミュレートできるようにし Broadcaster Clients.Others ます。

// For the complete example, see
// http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20
// 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);
    

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

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

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

public void Configuration(IAppBuilder app) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    app.MapSignalR();
}

オーバーライド可能な方法は多数あります。 完全な一覧については、「 HubPipelineModule メソッド」を参照してください。