SignalR w ASP.NET Core 2026: Komunikacja w Czasie Rzeczywistym, Huby i Pytania Rekrutacyjne

Kompleksowy przewodnik po SignalR w ASP.NET Core. Dowiedz się jak implementować komunikację w czasie rzeczywistym, konfigurować huby, zarządzać grupami i przygotować się do pytań rekrutacyjnych.

SignalR w ASP.NET Core - komunikacja w czasie rzeczywistym

SignalR umożliwia dwukierunkową komunikację w czasie rzeczywistym między serwerami a klientami w aplikacjach ASP.NET Core. W przeciwieństwie do tradycyjnego wzorca żądanie-odpowiedź HTTP, SignalR utrzymuje trwałe połączenia pozwalające serwerom natychmiastowo przesyłać aktualizacje do klientów — niezbędne dla aplikacji czatowych, dashboardów na żywo, wspólnej edycji dokumentów i powiadomień w czasie rzeczywistym.

Automatyczny Wybór Transportu SignalR

SignalR automatycznie wybiera najlepszy dostępny transport: najpierw WebSockets, następnie Server-Sent Events, a na końcu Long Polling. Ten mechanizm zapasowy zapewnia kompatybilność ze wszystkimi przeglądarkami i konfiguracjami sieciowymi bez konieczności wprowadzania zmian w kodzie.

Jak Huby SignalR Umożliwiają Komunikację Serwer-Klient

Hub działa jako wysokopoziomowy potok obsługujący wywołania metod między klientami a serwerami. Klienci wywołują metody na hubie, a hub może wywoływać metody na połączonych klientach. Ta abstrakcja eliminuje złożoność ręcznego zarządzania połączeniami WebSocket.

Poniższy przykład demonstruje podstawowy hub czatu, który rozgłasza wiadomości do wszystkich połączonych klientów:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Wywoływane gdy klient wysyła wiadomość
    public async Task SendMessage(string user, string message)
    {
        // Rozgłoszenie do WSZYSTKICH połączonych klientów
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // Wywoływane automatycznie gdy klient się łączy
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Wywoływane automatycznie gdy klient się rozłącza
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Czyszczenie zasobów, powiadamianie innych użytkowników, itp.
        await base.OnDisconnectedAsync(exception);
    }
}

Właściwość Clients zapewnia dostęp do wszystkich połączonych klientów poprzez różne opcje targetowania: All, Caller, Others, Group i User.

Konfiguracja SignalR w ASP.NET Core 9

Rejestracja SignalR w ASP.NET Core 9 wykorzystuje standardowy wzorzec middleware. Poniższa konfiguracja włącza protokoły JSON i MessagePack z niestandardowymi rozmiarami buforów:

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

// Dodanie usług SignalR z konfiguracją
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();

// Mapowanie endpointu huba
app.MapHub<ChatHub>("/chathub");

app.Run();

Ustawienia KeepAliveInterval i ClientTimeoutInterval kontrolują monitorowanie stanu połączenia. Klienci, którzy nie odpowiedzą w określonym czasie, są uznawani za rozłączonych.

Zarządzanie Grupami dla Ukierunkowanego Dostarczania Wiadomości

Grupy zapewniają mechanizm organizowania połączeń i wysyłania wiadomości do podzbiorów klientów. Typowym przypadkiem użycia są pokoje czatu, gdzie użytkownicy dołączają do konkretnych pokojów i otrzymują wiadomości tylko z tych pokojów.

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Dodanie bieżącego połączenia do grupy
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Powiadomienie grupy że ktoś dołączył
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Usunięcie bieżącego połączenia z grupy
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Wysłanie wiadomości tylko do konkretnej grupy
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

Członkostwo w grupie jest powiązane z połączeniami, nie z użytkownikami. Gdy użytkownik się ponownie łączy, musi ponownie dołączyć do swoich grup. Dla trwałego członkostwa w grupach należy przechowywać powiązania grup w bazie danych i automatycznie dołączać ponownie w OnConnectedAsync.

Trwałość Grup

Grupy istnieją tylko w pamięci i nie są utrwalane między restartami serwera lub w scenariuszach skalowania poziomego bez backplane. Dla wdrożeń produkcyjnych z wieloma serwerami należy używać Redis lub Azure SignalR Service jako backplane.

Zabezpieczanie Połączeń SignalR przez Uwierzytelnianie

SignalR integruje się z uwierzytelnianiem ASP.NET Core. Atrybut [Authorize] ogranicza dostęp do huba tylko dla uwierzytelnionych użytkowników, a Context.User zapewnia dostęp do claims uwierzytelnionego użytkownika.

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

[Authorize] // Wymaga uwierzytelnienia dla całego huba
public class SecureHub : Hub
{
    // Dostęp do claims użytkownika przez Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Wysłanie do konkretnego użytkownika (wszystkie jego połączenia)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Autoryzacja oparta na rolach dla metody
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

Dla połączeń WebSocket tokeny uwierzytelniania muszą być przekazywane przez query string, ponieważ WebSockets nie obsługują niestandardowych nagłówków. Konfiguracja klienta JavaScript do dołączania tokenu:

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

Po stronie serwera należy skonfigurować uwierzytelnianie JWT do odczytywania tokenu z query string:

Program.cs - Konfiguracja JWT dla SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Odczyt tokenu z query string dla 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;
            }
        };
    });

Gotowy na rozmowy o .NET?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Skalowanie SignalR z Redis Backplane

W środowisku z load balancerem klienci mogą łączyć się z różnymi instancjami serwera. Bez backplane wiadomości wysłane z jednego serwera docierają tylko do klientów połączonych z tym serwerem. Redis służy jako backplane do synchronizacji wiadomości między wszystkimi instancjami serwerów.

Program.cs - Konfiguracja Redis backplanecsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Opcjonalne: konfiguracja opcji specyficznych dla Redis
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

Redis backplane publikuje wszystkie wiadomości SignalR do kanałów Redis. Każda instancja serwera subskrybuje te kanały i dostarcza wiadomości do swoich lokalnych klientów. To podejście dodaje minimalne opóźnienie (zazwyczaj 1-2ms) jednocześnie umożliwiając skalowanie poziome.

Dla wdrożeń enterprise Azure SignalR Service zapewnia zarządzaną alternatywę, która automatycznie obsługuje skalowanie, zarządzanie połączeniami i dostępność.

Silnie Typowane Huby dla Bezpieczeństwa w Czasie Kompilacji

Silnie typowane huby zastępują magic strings metodami interfejsu, umożliwiając weryfikację wywołań metod klienta w czasie kompilacji:

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)
    {
        // Sprawdzanie w czasie kompilacji - bez magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // Autouzupełnianie IDE działa tutaj
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

Refaktoryzacja staje się bezpieczniejsza, ponieważ zmiana nazwy metody w interfejsie natychmiast pokazuje wszystkie miejsca wywołań wymagające aktualizacji.

Strumieniowanie Danych z Kanałami SignalR

SignalR obsługuje strumieniowanie dla scenariuszy gdzie dane są produkowane przyrostowo, takich jak aktualizacje postępu, śledzenie logów lub dane z czujników w czasie rzeczywistym. Obsługiwane jest zarówno strumieniowanie serwer-do-klienta jak i klient-do-serwera.

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Strumieniowanie serwer-do-klienta z 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);
        }
    }

    // Strumieniowanie klient-do-serwera
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Przetwarzanie każdego wpisu logu w miarę nadchodzenia
            await ProcessLogEntry(entry);
        }
    }
}

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

Klient JavaScript konsumuje strumień używając asynchronicznego iteratora:

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

Popularne Pytania Rekrutacyjne o SignalR

Rozmowy techniczne często obejmują architekturę SignalR i scenariusze z prawdziwego świata. Poniższe pytania regularnie pojawiają się podczas sesji rekrutacyjnych .NET:

Wskazówka Rekrutacyjna

Należy wyjaśnić proces negocjacji transportu: SignalR najpierw próbuje WebSockets, potem Server-Sent Events, a następnie Long Polling. Ważne jest zrozumienie kiedy każdy transport jest używany i jakie ma ograniczenia.

P: Jak SignalR obsługuje stan połączenia gdy klient tymczasowo traci łączność?

SignalR utrzymuje stan połączenia na serwerze przez konfigurowalny okres. Z włączonym automatycznym ponownym łączeniem klient próbuje ponownie się połączyć używając tego samego ID połączenia. Jeśli się powiedzie, połączenie jest wznawiane bez utraty członkostwa w grupach. Jeśli rozłączenie przekracza timeout, nawiązywane jest nowe połączenie i klient musi ponownie dołączyć do grup.

P: Jaka jest różnica między Clients.User() a Clients.Client()?

Clients.User(userId) targetuje wszystkie połączenia należące do konkretnego uwierzytelnionego użytkownika — przydatne gdy jeden użytkownik ma otwarte wiele kart przeglądarki. Clients.Client(connectionId) targetuje jedno konkretne połączenie. Targetowanie oparte na użytkowniku wymaga uwierzytelnienia i używa IUserIdProvider do mapowania połączeń na ID użytkowników.

P: Jak zaimplementować wykrywanie obecności (pokazywanie kto jest online)?

Należy śledzić połączenia w OnConnectedAsync i OnDisconnectedAsync. Mapowania użytkownik-do-połączenia przechowuje się w concurrent dictionary lub rozproszonej pamięci podręcznej. Dla wdrożeń ze skalowaniem poziomym używa się współdzielonego magazynu jak Redis. Aktualizacje obecności rozgłasza się do odpowiednich grup gdy użytkownicy się łączą lub rozłączają.

P: Wyjaśnij cel backplane SignalR.

Backplane synchronizuje wiadomości między wieloma instancjami serwerów w środowisku z load balancerem. Bez niego wiadomość wysłana z serwera A dociera tylko do klientów połączonych z serwerem A. Redis i Azure SignalR Service są popularnymi opcjami backplane. Backplane publikuje wiadomości do współdzielonego kanału, który subskrybują wszystkie serwery.

Strategie Optymalizacji Wydajności

Wydajność SignalR zależy od rozmiaru wiadomości, częstotliwości i liczby połączeń. Dokumentacja najlepszych praktyk wydajności ASP.NET Core zawiera szczegółowe benchmarki.

  • Używanie protokołu MessagePack dla serializacji binarnej — redukuje rozmiar payload o 30-50% w porównaniu do JSON
  • Grupowanie wiadomości gdy to możliwe zamiast wysyłania wielu małych wiadomości
  • Ograniczanie rozmiarów grup do tysięcy członków; dla większych audytoriów należy rozważyć wzorce pub/sub
  • Włączanie kompresji dla payload z dużą ilością tekstu
  • Ustawianie odpowiednich timeoutów aby szybko zwalniać zasoby z nieaktywnych połączeń
csharp
// Konfiguracja MessagePack dla mniejszych payloadów
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

Podsumowanie

  • SignalR abstrahuje wybór transportu (WebSockets, SSE, Long Polling) i zapewnia spójny model programowania dla komunikacji w czasie rzeczywistym
  • Huby centralizują zarządzanie połączeniami; silnie typowane huby dodają bezpieczeństwo w czasie kompilacji dla wywołań metod klienta
  • Grupy umożliwiają ukierunkowane dostarczanie wiadomości do podzbiorów połączeń — należy ponownie dołączać do grup po reconnect
  • Uwierzytelnianie integruje się z ASP.NET Core identity; tokeny przekazuje się przez query string dla połączeń WebSocket
  • Skalowanie poziome realizuje się z Redis backplane lub Azure SignalR Service do synchronizacji wiadomości między instancjami serwerów
  • Strumieniowanie obsługuje przyrostowe dostarczanie danych z IAsyncEnumerable zarówno dla scenariuszy serwer-do-klienta jak i klient-do-serwera

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Programista fullstack, założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 12 sierpnia 2026

Tagi

#signalr
#asp.net-core
#real-time
#websockets
#dotnet

Udostępnij

Powiązane artykuły