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 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.
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:
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:
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.
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.
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.
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:
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:
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.
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:
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.
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:
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:
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ń
// 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
IAsyncEnumerablezarówno dla scenariuszy serwer-do-klienta jak i klient-do-serwera
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Autor:
Anthony Fillion-MailletProgramista 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
Udostępnij
Powiązane artykuły

.NET MAUI w 2026: Kompletny Przewodnik po Programowaniu Cross-Platform i Pytania Rekrutacyjne
Poznaj .NET MAUI 10 w 2026 roku - od konfiguracji projektu, przez architekturę handlerów, MVVM z CommunityToolkit, HybridWebView, po migrację z Xamarin i kluczowe pytania rekrutacyjne.

.NET 9 Blazor: Pełnostackowy rozwój aplikacji z Blazor United w 2026
.NET 9 Blazor United łączy statyczny SSR, Server i WebAssembly w jednym frameworku full-stack. Praktyczny poradnik obejmujący tryby renderowania, streaming rendering, wstrzykiwanie zależności i wzorce produkcyjne.

Entity Framework Core: Optymalizacja wydajności i najlepsze praktyki w 2026
Kompleksowy przewodnik po optymalizacji EF Core 10. Poznaj techniki zwiększania wydajności zapytań, zarządzania pamięcią i skalowania aplikacji .NET.