Nowoczesne aplikacje webowe coraz częściej wymagają komunikacji w czasie rzeczywistym, użytkownicy oczekują, że dane pojawią się natychmiast bez odświeżania strony. Przykłady to chociażby powiadomienia w mediach społecznościowych, czaty online czy aktualizacje wyników na żywo. W ekosystemie .NET rozwiązaniem dla takiej komunikacji real-time jest biblioteka SignalR. W tym artykule wyjaśnię Ci, czym jest SignalR i jak z jego pomocą zbudować prostą funkcjonalność czasu rzeczywistego w aplikacji ASP.NET Core.
Co to jest SignalR i do czego służy?
SignalR to otwartoźródłowa biblioteka od Microsoftu, która upraszcza dodawanie funkcji web w czasie rzeczywistym do aplikacji ASP.NET Core. Mówiąc prościej, dzięki SignalR nasz kod serwerowy może natychmiast przesyłać dane do połączonych klientów, gdy tylko dane te się pojawią. Nie trzeba już ciągle odpytywać serwera ani ręcznie odświeżać strony.
SignalR działa w oparciu o mechanizm tzw. hubów. Hub to klasa C# na serwerze (dziedzicząca po Hub), która pozwala klientom i serwerowi wzajemnie wywoływać swoje metody (to tzw. wywołania RPC). SignalR zajmuje się całą niskopoziomową komunikacją, utrzymuje i śledzi aktywne połączenia, serializuje wiadomości i automatycznie wybiera najlepszy transport dostępny pomiędzy serwerem a klientem (WebSockets, Server-Sent Events, Long Polling). Dzięki temu programista może skupić się na logice aplikacji, a nie na implementowaniu protokołów sieciowych.
Jakie scenariusze typowo obsłużymy za pomocą SignalR? Oto kilka przykładów zastosowań real-time:
• Czat lub komunikator online: wiadomości od jednego użytkownika natychmiast trafiają do pozostałych uczestników.
• Powiadomienia w aplikacjach SPA/social media: np. alert o nowej wiadomości, lajku czy komentarzu wyświetla się od razu wszystkim odpowiednim użytkownikom.
• Dashboardy i monitoring na żywo: firmowe panele z danymi (np. sprzedaż, wyniki sportowe) aktualizujące się automatycznie w momencie pojawienia się nowych danych.
• Gry multiplayer lub aplikacje współpracy: stan gry lub wspólna tablica są synchronizowane w czasie rzeczywistym między wszystkimi klientami.
Wszędzie tam, gdzie do tej pory stosowano częste odświeżanie strony lub długie odpytywanie AJAX, SignalR pozwala usprawnić działanie aplikacji. Co ważne, SignalR działa cross-platform, klienci mogą być napisani nie tylko w JavaScript (przeglądarka), ale istnieją też biblioteki klienckie dla .NET (C#, F#), Java, a nawet aplikacji mobilnych (np. Xamarin/MAUI). Dzięki temu ten sam hub może obsługiwać np. stronę web oraz aplikację mobilną jednocześnie.
Jak zbudować aplikację czasu rzeczywistego z SignalR?
Przyjrzyjmy się, jak krok po kroku dodać SignalR do prostej aplikacji ASP.NET Core. Załóżmy scenariusz czatu na żywo, kilku użytkowników wpisuje wiadomości, które od razu pojawiają się u wszystkich. Integracja SignalR w projekcie wygląda następująco:
1. Konfiguracja serwera: Dodaj usługę SignalR podczas konfiguracji aplikacji oraz zarejestruj endpoint dla swojego huba. W pliku startowym (np. Program.cs) wywołujemy builder.Services.AddSignalR() i dodajemy trasę, np. app.MapHub<ChatHub>("/chat"), dzięki czemu żądania pod URL /chat będą obsługiwane przez nasz hub.
2. Tworzenie klasy Hub: Definiujemy klasę huba po stronie serwera, która dziedziczy z Hub. W tej klasie tworzymy metody, które klienci będą mogli wywoływać. Przykładowo metoda SendMessage może przyjmować treść wiadomości i rozesłać ją do wszystkich aktualnie podłączonych użytkowników.
3. Dodanie biblioteki klienckiej: Biblioteka serwerowa SignalR jest wbudowana w ASP.NET Core, natomiast po stronie klienta (np. w JavaScript na stronie) musimy dodać odpowiedni skrypt SignalR. Można to zrobić, dołączając paczkę npm @microsoft/signalr lub używając CDN z plikiem signalr.js. (Domyślnie projekt ASP.NET Core nie zawiera plików klienta SignalR).
4. Kod klienta: W aplikacji front-end piszemy kod nawiązujący połączenie z naszym hubem i obsługujący komunikaty. W przypadku web klienta będzie to JavaScript: tworzymy połączenie przez HubConnectionBuilder, następnie rejestrujemy funkcję obsługującą zdarzenie (np. odbiór komunikatu od serwera), uruchamiamy połączenie i w odpowiednim momencie wysyłamy dane do serwera metodą invoke (co wywoła naszą metodę huba na serwerze).
Poniżej znajduje się przykład minimalnej implementacji huba czatu po stronie serwera oraz odpowiadającego mu fragmentu kodu po stronie klienta:
/* Serwer: prosty hub rozsyłający wiadomości do wszystkich klientów */
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
/* Wysyła otrzymaną wiadomość do *wszystkich* podłączonych klientów: */
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}Powyższy hub (ChatHub) dziedziczy po bazowej klasie Hub. Metoda SendMessage zostanie wywołana przez klienta i następnie asychronicznie prześle wiadomość do wszystkich klientów poprzez Clients.All.SendAsync. W ten sposób każdy podłączony klient otrzyma zdarzenie ReceiveMessage wraz z parametrami user (nadawca) i message (treść). Kod SignalR jest asynchroniczny, dzięki czemu łatwo skaluje się na dużą liczbę równoczesnych połączeń.
Po stronie klienta (np. w przeglądarce) potrzebujemy nawiązać połączenie i nasłuchiwać na zdarzenia. Zakładając, że na stronie dołączyliśmy już plik signalr.js, kod JavaScript może wyglądać tak:
/* Klient (JavaScript): nawiązanie połączenia z hubem i obsługa komunikatów */
const connection = new signalR.HubConnectionBuilder().withUrl("/chat").build();
/* Rejestracja funkcji obsługującej odbiór wiadomości od serwera: */
connection.on("ReceiveMessage", (user, message) => {
console.log(`${user} napisał: ${message}`);
});
/* Uruchomienie połączenia z serwerem: */
connection.start().then(() => {
/* Przykładowe wysłanie wiadomości po nawiązaniu połączenia: */
connection.invoke("SendMessage", "Jan", "Witaj wszystkim!");
});W powyższym kodzie klienta ustawiamy adres naszego huba (/chat - musi on odpowiadać ścieżce zarejestrowanej w aplikacji). Metoda connection.on("ReceiveMessage", ...) definiuje, co zrobić, gdy serwer wywoła u nas metodę ReceiveMessage, tutaj po prostu logujemy wiadomość do konsoli (w realnej aplikacji można np. zaktualizować interfejs strony, dodając nową wiadomość do listy czatu). Po wywołaniu connection.start() nawiązujemy fizyczne połączenie web socket (bądź inny transport) z serwerem. Gdy połączenie jest gotowe, wywołujemy connection.invoke("SendMessage", ...) - to spowoduje wywołanie metody SendMessage naszego huba na serwerze, przekazując jej podane argumenty (np. nazwę użytkownika i treść komunikatu). Serwer odbierze te dane w metodzie huba i rozgłosi wiadomość do wszystkich nasłuchujących klientów, w tym także do nadawcy. Cały cykl odbywa się w ułamku sekundy i użytkownicy od razu widzą nową wiadomość, oto komunikacja w czasie rzeczywistym w praktyce.
Podsumowanie
Dzięki SignalR dodanie funkcjonalności czasu rzeczywistego do aplikacji ASP.NET Core jest szybkie i stosunkowo proste. Biblioteka ta abstrahuje od nas skomplikowane aspekty utrzymywania połączeń i komunikacji dwukierunkowej, pozwalając skupić się na logice aplikacyjnej. W efekcie nawet niewielkim nakładem pracy możemy dostarczyć użytkownikom nowoczesne, interaktywne aplikacje reagujące natychmiast na zdarzenia po stronie serwera.
Jeśli zainteresowało Cię to zagadnienie i chcesz dalej rozwijać swoje umiejętności w ASP.NET Core, nie tylko w kontekście SignalR, ale również budowania zaawansowanych aplikacji webowych (MVC, ASP.NET Core Web API i nie tylko), zachęcam do sprawdzenia mojego szkolenia online Szkoła ASP.NET Core. To praktyczne szkolenie, w którym krok po kroku tworzymy rozbudowaną aplikację w ASP.NET Core, dzięki czemu opanujesz kluczowe koncepcje i najlepsze praktyki przydatne na co dzień w pracy programisty .NET.