ASP.NET SignalR Hubs API ガイド - サーバー (C#)
Patrick Fletcher、Tom Dykstra 著
警告
このドキュメントは、最新版の SignalR を対象としていません。 ASP.NET Core SignalR に関する記事を参照してください。
このドキュメントでは、SignalR バージョン 2 用の ASP.NET SignalR Hubs API のサーバー側のプログラミングの概要を説明し、一般的なオプションを説明するためのコード サンプルを示します。
SignalR Hubs API を使うと、サーバーから接続されたクライアントへ、およびクライアントからサーバーへのリモート プロシージャ コール (RPC) を行うことができます。 サーバー コードでは、クライアントから呼び出すことができるメソッドを定義し、クライアント上で実行されるメソッドを呼び出します。 クライアント コードでは、サーバーから呼び出すことができるメソッドを定義し、サーバー上で実行されるメソッドを呼び出します。 SignalR は、クライアントからサーバーへの配管系統をすべて処理します。
SignalR は、永続的接続と呼ばれる下位レベルの API も提供します。 SignalR、ハブ、永続的接続の概要については、「SignalR 2 の概要」を参照してください。
このトピックで使用するソフトウェアのバージョン
- Visual Studio 2013
- .NET 4.5
- 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 スタートアップ クラスを使って SignalR Hubs のルートを定義する方法を示しています。
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 Hubs API ガイド - JavaScript クライアント - 生成されたプロキシとその機能」を参照してください)。
特殊な状況により、このベース 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 Hubs API ガイド - JavaScript クライアント - クロスドメイン接続を確立する方法」を参照してください。詳細なエラー メッセージを有効にする。
エラーが発生した場合、SignalR の既定の動作では、何が起こったのかについての詳細を含まない通知メッセージがクライアントに送信されます。 悪意のあるユーザーがアプリケーションに対する攻撃にその情報を使用できる可能性があるため、運用環境では詳細なエラー情報をクライアントに送信することはお勧めできません。 トラブルシューティングの場合、このオプションを使って、より有益なエラー レポートを一時的に有効にすることができます。
自動的に生成された JavaScript プロキシ ファイルを無効にする。
既定では、ハブ クラスのプロキシを含む JavaScript ファイルが、URL "/signalr/hubs" に応答して生成されます。 JavaScript プロキシを使いたくない場合、またはこのファイルを手動で生成してクライアント内の物理ファイルを参照したい場合は、このオプションを使ってプロキシ生成を無効にすることができます。 詳細については、「SignalR Hubs API ガイド - JavaScript クライアント - SignalR で生成されたプロキシの物理ファイルを作成する方法」を参照してください。
次の例は、MapSignalR
メソッドの呼び出しで SignalR 接続 URL とこれらのオプションを指定する方法を示しています。 カスタム URL を指定するには、例にある "/signalr" を使用する URL に置き換えます。
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);
ハブ クラスの作成と使用の方法
ハブを作成するには、Microsoft.Aspnet.Signalr.Hub から派生するクラスを作成します。 次の例は、チャット アプリケーションの単純なハブ クラスを示しています。
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await 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
クラスの別のクライアント セットを参照します。
厳密に型指定されたハブ
クライアントが参照できる (そしてハブ メソッドで Intellisense を有効にする) ハブ メソッドのインターフェイスを定義するには、Hub
ではなく Hub<T>
(SignalR 2.1 で導入) からハブを派生させます。
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 クライアントでのメソッド名のキャメルケース化
既定では、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();
}
非同期
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 サービスが例外をスローします。
ハブ メソッド呼び出しの進行状況のレポート
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 を受信するクライアントの選択
クライアント プロパティは、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、引数...) を呼び出します。
public async Task NewContosoChatMessage(string name, string message)
{
string methodToCall = "addContosoChatMessageToPage";
IClientProxy proxy = Clients.All;
await 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
メソッドが完了していることを確認する必要があります。 次のコード例は、その方法を示しています。
クライアントをグループに追加し、そのクライアントにメッセージを送信
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、OnDisconnected、OnReconnected が呼び出されるとき
ブラウザーが新しいページに移動するたびに、新しい接続を確立する必要があります。つまり、SignalR は OnDisconnected
メソッドを、続いて OnConnected
メソッドを実行します。 SignalR は、新しい接続が確立されると常に新しい接続 ID を作成します。
OnReconnected
メソッドは、ケーブルが一時的に取り外され、接続がタイム アウトになる前に再接続された場合など、SignalR が自動的に回復できる接続が一時的に切断されたときに呼び出されます。OnDisconnected
メソッドは、ブラウザーが新しいページに移動したときなど、クライアントが切断され、SignalR が自動的に再接続できないときに呼び出されます。 そのため、所与のクライアントで考えられるイベントのシーケンスは、OnConnected
、OnReconnected
、OnDisconnected
か、あるいは OnConnected
、OnDisconnected
です。 所与の接続に対し、シーケンス OnConnected
、OnDisconnected
、OnReconnected
は起こりません。
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
プロパティは、接続の有効期間イベント ハンドラー メソッド 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
を使って非同期操作 (クライアント メソッドの呼び出しなど) からの例外を "監視" するようにします。 その後、次のいずれかまたは複数の方法を実行します。
メソッド コードを try-catch ブロックでラップし、例外オブジェクトをログします。 デバッグ目的ではクライアントに例外を送信できますが、セキュリティ上の理由から、運用環境のクライアントに詳細情報を送信することはお勧めできません。
OnIncomingError メソッドを処理する Hubs パイプライン モジュールを作成します。 次の例は、エラーをログするパイプライン モジュールと、その後にモジュールを Hubs パイプラインに挿入する 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); }
ハブ パイプライン モジュールの詳細については、このトピックで後述する「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/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.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
をシミュレートできます。 次の例では、Broadcaster
クラスが Clients.Others
をシミュレートできるように、MoveShapeHub
クラスが接続 ID を Broadcaster
クラスに渡します。
// 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);
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);
}
}
Startup.cs ファイル内の次のコードは、ハブ パイプラインで実行するモジュールを登録します。
public void Configuration(IAppBuilder app)
{
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
}
オーバーライドできるさまざまなメソッドがあります。 完全な一覧については、「HubPipelineModule メソッド」を参照してください。