ASP.NET Core 2026のSignalR完全ガイド:リアルタイム通信、Hub、面接対策

ASP.NET CoreにおけるSignalRでリアルタイムWebアプリケーションを構築する方法を解説。Hub、グループ、Redisによるスケーリング、技術面接頻出問題まで網羅。

ASP.NET Core 2026のSignalR完全ガイド:リアルタイム通信、Hub、面接対策

SignalRは、ASP.NET Coreアプリケーションにおいてサーバーとクライアント間の双方向リアルタイム通信を実現するライブラリです。従来のHTTPリクエスト・レスポンスパターンとは異なり、SignalRは永続的な接続を維持し、サーバーからクライアントへ即座にデータをプッシュできます。チャットアプリケーション、ライブダッシュボード、共同編集ツール、リアルタイム通知システムなど、即時性が求められるアプリケーションに不可欠な技術です。

SignalRのトランスポートフォールバック

SignalRは利用可能な最適なトランスポートを自動選択します。まずWebSocketsを試行し、次にServer-Sent Events、最後にLong Pollingへフォールバックします。このメカニズムにより、コード変更なしで全てのブラウザとネットワーク構成に対応できます。

SignalR Hubによるサーバー・クライアント間通信の仕組み

Hubは、クライアントとサーバー間のメソッド呼び出しを処理する高レベルのパイプラインとして機能します。クライアントはHub上のメソッドを呼び出し、HubはConnected状態のクライアント上のメソッドを呼び出すことができます。この抽象化により、WebSocket接続を手動で管理する複雑さが解消されます。

以下の例は、接続中の全クライアントにメッセージをブロードキャストする基本的なチャットHubを示しています:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Called when a client sends a message
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to ALL connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // Called automatically when a client connects
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Called automatically when a client disconnects
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Clean up resources, notify other users, etc.
        await base.OnDisconnectedAsync(exception);
    }
}

Clientsプロパティは、AllCallerOthersGroupUserなど様々なターゲティングオプションを通じて、全ての接続クライアントへのアクセスを提供します。

ASP.NET Core 9におけるSignalRの設定

ASP.NET Core 9でのSignalR登録は、標準的なミドルウェアパターンに従います。以下の設定では、JSONとMessagePackプロトコルをカスタムバッファサイズで有効にしています:

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Add SignalR services with configuration
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
    options.StreamBufferCapacity = 10;
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddJsonProtocol(options =>
{
    options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase
});

var app = builder.Build();

// Map the hub endpoint
app.MapHub<ChatHub>("/chathub");

app.Run();

KeepAliveIntervalClientTimeoutIntervalの設定は、接続の健全性監視を制御します。タイムアウト期間内に応答しないクライアントは切断されたものとみなされます。

グループを活用したターゲットメッセージ配信

グループは、接続を整理し、クライアントのサブセットにメッセージを送信するメカニズムを提供します。典型的なユースケースはチャットルームで、ユーザーが特定のルームに参加し、そのルームからのメッセージのみを受信します。

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Add the current connection to a group
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Notify the group that someone joined
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Remove the current connection from a group
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Send a message to a specific group only
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

グループメンバーシップは接続に紐づいており、ユーザーには紐づいていません。ユーザーが再接続した場合、グループに再参加する必要があります。永続的なグループメンバーシップを実現するには、グループの関連付けをデータベースに保存し、OnConnectedAsyncで自動的に再参加します。

グループの永続化について

グループはメモリ上にのみ存在し、サーバー再起動時やバックプレーンなしのスケールアウトシナリオでは永続化されません。複数サーバーを使用する本番デプロイメントでは、RedisまたはAzure SignalR Serviceをバックプレーンとして使用してください。

認証によるSignalR接続のセキュリティ保護

SignalRはASP.NET Core認証と統合されています。[Authorize]属性でHubアクセスを認証済みユーザーに制限し、Context.Userで認証済みユーザーのクレームにアクセスできます。

SecureHub.cscsharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

[Authorize] // Require authentication for the entire hub
public class SecureHub : Hub
{
    // Access user claims through Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Send to a specific user (all their connections)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Role-based authorization on method
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

WebSocket接続では、カスタムヘッダーをサポートしていないため、認証トークンをクエリ文字列経由で渡す必要があります。JavaScriptクライアントでトークンを含める設定は以下の通りです:

signalr-client.tstypescript
import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/securehub", {
        accessTokenFactory: () => localStorage.getItem("authToken") || ""
    })
    .withAutomaticReconnect()
    .build();

await connection.start();

サーバー側では、クエリ文字列からトークンを読み取るようJWT認証を設定します:

Program.cs - JWT configuration for SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Read token from query string for SignalR
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) && 
                    path.StartsWithSegments("/securehub"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

.NETの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Redisバックプレーンによるスケーリング

ロードバランス環境では、クライアントが異なるサーバーインスタンスに接続する可能性があります。バックプレーンがない場合、あるサーバーから送信されたメッセージは、そのサーバーに接続しているクライアントにのみ届きます。Redisは、全サーバーインスタンス間でメッセージを同期するバックプレーンとして機能します。

Program.cs - Redis backplane configurationcsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Optional: configure Redis-specific options
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

Redisバックプレーンは、全てのSignalRメッセージをRedisチャネルにパブリッシュします。各サーバーインスタンスはこれらのチャネルをサブスクライブし、ローカルクライアントにメッセージを配信します。このアプローチでは最小限のレイテンシ(通常1〜2ms)で水平スケーリングが可能になります。

エンタープライズデプロイメントでは、Azure SignalR Serviceがスケーリング、接続管理、可用性を自動的に処理するマネージドな代替手段を提供します。

型安全なHubによるコンパイル時検証

強く型付けされたHubは、マジックストリングをインターフェースメソッドに置き換え、クライアントメソッド呼び出しのコンパイル時検証を可能にします:

INotificationClient.cscsharp
public interface INotificationClient
{
    Task ReceiveNotification(string title, string message, string severity);
    Task UpdateProgress(int percentage);
    Task TaskCompleted(Guid taskId, bool success);
}

// NotificationHub.cs
public class NotificationHub : Hub<INotificationClient>
{
    public async Task NotifyAll(string title, string message)
    {
        // Compile-time checking - no magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // IDE autocomplete works here
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

インターフェースのメソッド名を変更すると、更新が必要な全ての呼び出し箇所が即座に表示されるため、リファクタリングがより安全になります。

SignalRチャネルによるストリーミング

SignalRは、進捗更新、ログのテーリング、リアルタイムセンサーデータなど、データが段階的に生成されるシナリオ向けのストリーミングをサポートしています。サーバーからクライアント、クライアントからサーバーの両方向のストリーミングが可能です。

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Server-to-client streaming with IAsyncEnumerable
    public async IAsyncEnumerable<StockPrice> StreamStockPrices(
        string[] symbols,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var random = new Random();
        
        while (!cancellationToken.IsCancellationRequested)
        {
            foreach (var symbol in symbols)
            {
                yield return new StockPrice
                {
                    Symbol = symbol,
                    Price = random.NextDouble() * 1000,
                    Timestamp = DateTime.UtcNow
                };
            }
            
            await Task.Delay(1000, cancellationToken);
        }
    }

    // Client-to-server streaming
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Process each log entry as it arrives
            await ProcessLogEntry(entry);
        }
    }
}

public record StockPrice(string Symbol, double Price, DateTime Timestamp);
public record LogEntry(string Level, string Message, DateTime Timestamp);

JavaScriptクライアントは非同期イテレータを使用してストリームを消費します:

streaming-client.tstypescript
const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]);

stream.subscribe({
    next: (price) => console.log(`${price.symbol}: $${price.price}`),
    error: (err) => console.error(err),
    complete: () => console.log("Stream completed")
});

SignalR技術面接頻出問題

技術面接では、SignalRのアーキテクチャと実務シナリオが頻繁に取り上げられます。以下は.NET面接セッションで定期的に出題される問題です:

面接対策のポイント

トランスポートネゴシエーションプロセスを説明できるようにしましょう:SignalRはまずWebSocketsを試行し、Server-Sent Events、Long Pollingの順にフォールバックします。各トランスポートがいつ使用され、その制限は何かを理解しておくことが重要です。

Q: クライアントが一時的に接続を失った場合、SignalRはどのように接続状態を処理しますか?

SignalRは設定可能な期間、サーバー上で接続状態を維持します。自動再接続が有効な場合、クライアントは同じ接続IDを使用して再接続を試みます。成功すれば、グループメンバーシップを失わずに接続が再開されます。切断がタイムアウトを超えた場合は、新しい接続が確立され、クライアントはグループに再参加する必要があります。

Q: Clients.User()Clients.Client()の違いは何ですか?

Clients.User(userId)は、特定の認証済みユーザーに属する全ての接続をターゲットにします。これは、1人のユーザーが複数のブラウザタブを開いている場合に便利です。Clients.Client(connectionId)は、単一の特定の接続をターゲットにします。ユーザーベースのターゲティングには認証が必要で、IUserIdProviderを使用して接続をユーザーIDにマッピングします。

Q: プレゼンス検出(オンラインユーザーの表示)をどのように実装しますか?

OnConnectedAsyncOnDisconnectedAsyncで接続を追跡します。ユーザーと接続のマッピングをConcurrentDictionaryまたは分散キャッシュに保存します。スケールアウトされたデプロイメントでは、Redisなどの共有ストアを使用します。ユーザーの接続または切断時に、関連するグループにプレゼンス更新をブロードキャストします。

Q: SignalRバックプレーンの目的を説明してください。

バックプレーンは、ロードバランス環境における複数のサーバーインスタンス間でメッセージを同期します。バックプレーンがない場合、サーバーAから送信されたメッセージは、サーバーAに接続しているクライアントにのみ届きます。RedisとAzure SignalR Serviceが一般的なバックプレーンオプションです。バックプレーンは、全サーバーがサブスクライブする共有チャネルにメッセージをパブリッシュします。

パフォーマンス最適化戦略

SignalRのパフォーマンスは、メッセージサイズ、頻度、接続数に依存します。ASP.NET Coreパフォーマンスベストプラクティスのドキュメントでは、詳細なベンチマークが提供されています。

  • MessagePackプロトコルを使用:バイナリシリアライゼーションにより、JSONと比較してペイロードサイズを30〜50%削減
  • メッセージをバッチ処理:多数の小さなメッセージを送信する代わりに、可能な限りまとめる
  • グループサイズを制限:メンバー数は数千に抑える。より大規模な配信にはpub/subパターンを検討
  • 圧縮を有効化:テキスト中心のペイロードに対して有効
  • 適切なタイムアウトを設定:古い接続からのリソースを速やかに解放
csharp
// MessagePack configuration for smaller payloads
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

まとめ

  • SignalRはトランスポート選択(WebSockets、SSE、Long Polling)を抽象化し、リアルタイム通信のための一貫したプログラミングモデルを提供します
  • Hubは接続管理を一元化し、強く型付けされたHubはクライアントメソッド呼び出しにコンパイル時の安全性を追加します
  • グループは接続のサブセットへのターゲットメッセージ配信を可能にします。再接続後はグループへの再参加が必要です
  • 認証はASP.NET Coreアイデンティティと統合されます。WebSocket接続ではクエリ文字列経由でトークンを渡します
  • Redisバックプレーンまたは Azure SignalR Serviceを使用して、サーバーインスタンス間でメッセージを同期し、水平スケーリングを実現します
  • ストリーミングはIAsyncEnumerableを使用した段階的なデータ配信をサポートし、サーバーからクライアント、クライアントからサーバーの両方向で利用可能です

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

フルスタック開発者、SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月12日 更新

共有

関連記事