ASP.NET Core 用 SignalR でハブを使用する

作成者: Rachel AppelKevin Griffin

サンプル コードを表示またはダウンロードする (ダウンロード方法)

SignalR ハブとは

SignalR Hubs API を使用すると、接続されているクライアント上のメソッドをサーバーから呼び出すことができます。 サーバーのコードでは、クライアントによって呼び出されるメソッドを定義します。 クライアントのコードでは、サーバーから呼び出されるメソッドを定義します。 クライアントからサーバーおよびサーバーからクライアントへのリアルタイム通信を可能にするバックグラウンドの処理はすべて、SignalR によって行われます。

SignalR ハブを構成する

SignalR ミドルウェアに必要ないくつかのサービスは、services.AddSignalR を呼び出すと構成されます。

services.AddSignalR();

ASP.NET Core アプリに SignalR 機能を追加するときは、Startup.Configure メソッドの app.UseEndpoints コールバックで endpoint.MapHub を呼び出して SignalR ルートをセットアップします。

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/chathub");
});

ASP.NET Core アプリに SignalR 機能を追加するときは、Startup.Configure メソッドで app.UseSignalR を呼び出して SignalR ルートをセットアップします。

app.UseSignalR(route =>
{
    route.MapHub<ChatHub>("/chathub");
});

ハブを作成して使用する

Hub から継承するクラスを宣言してハブを作成し、それにパブリック メソッドを追加します。 クライアントは、public として定義されているメソッドを呼び出すことができます。

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

C# のメソッドの場合と同様に、複合型や配列など、戻り値の型とパラメーターを指定できます。 パラメーターと戻り値の複合オブジェクトおよび配列のシリアル化と逆シリアル化は、SignalR によって処理されます。

注意

ハブは一時的なものです。

  • ハブ クラスのプロパティに状態を格納しないでください。 ハブ メソッドのすべての呼び出しは、新しいハブ インスタンスで実行されます。
  • ハブが存在し続けることに依存する非同期メソッドを呼び出すときは、await を使います。 たとえば、Clients.All.SendAsync(...) などのメソッドは、await を指定せずに呼び出し、SendAsync が終了する前にハブ メソッドが完了した場合、失敗する可能性があります。

Context オブジェクト

Hub クラスの Context プロパティには、接続に関する情報が設定される次のプロパティが含まれます。

プロパティ 説明
ConnectionId SignalR によって割り当てられる、接続の一意の ID を取得します。 接続ごとに 1 つの接続 ID があります。
UserIdentifier ユーザー識別子を取得します。 既定では、SignalR は、接続に関連付けられている ClaimsPrincipal からの ClaimTypes.NameIdentifier をユーザー識別子として使用します。
User 現在のユーザーに関連付けられている ClaimsPrincipal を取得します。
Items この接続のスコープ内でデータを共有するために使用できるキーと値のコレクションを取得します。 このコレクションにデータを格納することができ、ハブ メソッドの異なる呼び出しの間も、その接続について維持されます。
Features 接続で使用できる機能のコレクションを取得します。 現時点では、ほとんどのシナリオでこのコレクションは必要ないため、まだ詳細には記載されていません。
ConnectionAborted 接続が中止されたときに通知する CancellationToken を取得します。

Hub.Context には、次のメソッドも含まれます。

メソッド 説明
GetHttpContext 接続の HttpContext を返します。接続が HTTP 要求に関連付けられていない場合は null を返します。 HTTP 接続の場合は、このメソッドを使って、HTTP ヘッダーやクエリ文字列などの情報を取得できます。
Abort 接続を中止します。

Clients オブジェクト

Hub クラスの Clients プロパティには、サーバーとクライアントの間の通信に関する次のプロパティが含まれます。

プロパティ 説明
All 接続されているすべてのクライアントでメソッドを呼び出します
Caller ハブ メソッドを呼び出したクライアントでメソッドを呼び出します
Others メソッドを呼び出したクライアントを除く、接続されているすべてのクライアントでメソッドを呼び出します

Hub.Clients には、次のメソッドも含まれます。

メソッド 説明
AllExcept 指定した接続を除く、接続されているすべてのクライアントでメソッドを呼び出します
Client 接続された特定の 1 つのクライアントでメソッドを呼び出します
Clients 接続された特定の複数のクライアントでメソッドを呼び出します
Group 指定したグループ内のすべての接続でメソッドを呼び出します
GroupExcept 指定した接続を除く、指定したグループ内のすべての接続でメソッドを呼び出します
Groups 複数の接続グループでメソッドを呼び出します
OthersInGroup ハブ メソッドを呼び出したクライアントを除く、接続のグループでメソッドを呼び出します
User 特定のユーザーに関連付けられているすべての接続でメソッドを呼び出します
Users 指定したユーザーに関連付けられているすべての接続でメソッドを呼び出します

SendAsync メソッドでは、前の表の各プロパティまたはメソッドからオブジェクトが返されます。 SendAsync メソッドを使用すると、呼び出すクライアント メソッドの名前とパラメーターを指定できます。

クライアントにメッセージを送信する

特定のクライアントに対して呼び出しを行うには、Clients オブジェクトのプロパティを使用します。 次の例には、3 つのハブ メソッドがあります。

  • SendMessage を呼び出すと、Clients.All を使用して、接続されているすべてのクライアントにメッセージが送信されます。
  • SendMessageToCaller を呼び出すと、Clients.Caller を使用して、呼び出し元にメッセージが返送されます。
  • SendMessageToGroup を呼び出すと、SignalR Users グループ内のすべてのクライアントにメッセージが送信されます。
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

厳密に型指定されたハブ

SendAsync を使用する場合の欠点は、呼び出されるクライアント メソッドを指定するのに、マジック文字列が使用されることです。 このため、メソッド名が間違って綴られていたり、クライアントに存在しなくなっていると、コードで実行時エラーが発生します。

SendAsync を使用する代わりに、Hub<T> を使用して Hub を厳密に型指定する方法もあります。 次の例では、ChatHub クライアント メソッドが IChatClient というインターフェイスに抽出されています。

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

このインターフェイスを使用して、前の ChatHub の例をリファクタリングすることができます。

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.ReceiveMessage(user, message);
    }

    public Task SendMessageToCaller(string user, string message)
    {
        return Clients.Caller.ReceiveMessage(user, message);
    }
}

Hub<IChatClient> を使用すると、クライアント メソッドをコンパイル時にチェックできます。 これにより、Hub<T> では、インターフェイスで定義されたメソッドへのアクセスのみを提供できるため、マジック文字列の使用による問題を回避できます。

厳密に型指定された Hub<T> を使用すると、SendAsync は使用できなくなります。 インターフェイスで定義されているどのメソッドも、引き続き非同期として定義できます。 実際、これらの各メソッドからは Task を返す必要があります。 これはインターフェイスであるため、async キーワードを使用しないでください。 次に例を示します。

public interface IClient
{
    Task ClientMethod();
}

注意

メソッド名からは Async サフィックスが除かれていません。 クライアント メソッドが .on('MyMethodAsync') で定義されていない場合は、名前として MyMethodAsync を使用しないでください。

ハブ メソッドの名前を変更する

既定では、サーバー ハブ メソッドの名前は .NET メソッドの名前です。 ただし、HubMethodName 属性を使ってこの既定値を変更し、メソッドの名前を手動で指定することができます。 クライアントでメソッドを呼び出すときは、.NET のメソッド名ではなく、この名前を使用する必要があります。

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

接続のイベントを処理する

SignalR Hubs API には、接続の管理と追跡のために、仮想メソッド OnConnectedAsyncOnDisconnectedAsync が用意されています。 クライアントがハブに接続するときにアクションを実行するには (グループへの追加など)、OnConnectedAsync 仮想メソッドをオーバーライドします。

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

クライアントが切断するときにアクションを実行するには、OnDisconnectedAsync 仮想メソッドをオーバーライドします。 クライアントが意図的に切断された場合 (たとえば、connection.stop() を呼び出すことによって)、exception パラメーターは null になります。 一方、エラーのためにクライアントが切断された場合は (ネットワーク障害など)、exception パラメーターにエラーを説明する例外が格納されます。

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnDisconnectedAsync(exception);
}

警告

セキュリティ警告: ConnectionId を公開すると、サーバーまたはクライアントのバージョンが SignalR ASP.NET Core 2.2 以前である場合、偽装される可能性があります。

エラーの処理

ハブ メソッドでスローされた例外は、メソッドを呼び出したクライアントに送信されます。 JavaScript クライアントでは、invoke メソッドによって JavaScript Promise が返されます。 クライアントが catch を使用してハンドラーが Promise にアタッチされたエラーを受け取ると、それが呼び出されて、JavaScript の Error オブジェクトとして渡されます。

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

ハブが例外をスローした場合、接続は閉じられません。 既定では、SignalR からは一般的なエラー メッセージがクライアントに返されます。 次に例を示します。

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

予期しない例外には、データベース接続が失敗したときにトリガーされる例外でのデータベース サーバー名など、機密情報が含まれていることがよくあります。 セキュリティ対策として、SignalR では、これらの詳細なエラー メッセージは既定では公開されません。 例外の詳細が抑制される理由について詳しくは、セキュリティの考慮事項に関する記事をご覧ください。

例外の状態をクライアントに伝える必要が "ある" 場合は、HubException クラスを使用できます。 ハブ メソッドから HubException をスローすると、SignalR によってメッセージ全体が変更されずにクライアントに 送信されます

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

注意

SignalR によってクライアントに送信されるのは、例外の Message プロパティだけです。 スタック トレースと例外のその他のプロパティは、クライアントでは使用できません。