SignalR jest bardzo dobrym narzędziem pozwalającym na komunikację pomiędzy frontendem oraz backendem (w formie API), która odbywa się w obie strony – zazwyczaj to frontend komunikował się z API gdy potrzebował dostać dane, a SignalR pozwala na komunikację również w drugą stronę, gdy to API chce poinformować frontend o nowym zdarzeniu. Przykładów użycia jest mnóstwo, choćby wszystkim znana aplikacja do czatu – wszystkie wiadomości są na serwerze (naszym API), a frontend je wyświetla – jak ma się dowiedzieć, że przyszła nowa wiadomość? Właśnie wtedy, gdy nowa wiadomość jest tworzona, API może poinformować wszystkie frontendy, które go “nasłuchują” o tym, że jest nowa wiadomość – i do tego celu wykorzystamy SignalR.
Dla SignalR i aplikacji ASP.NET Core nie ma to znaczenia czego użyjemy jako frontend – działa on w większości obecnie dostępnych frameworków frontendowych takich jak Angular, React, Vue, Blazor i wiele innych. W tym artykule przyjrzymy się dokładnie jak zrobić to konkretnie w Angularze, w obecnie najnowszej wersji – 8.
SignalR od strony API
Zaczniemy od backendu. Używamy oczywiście Visual Studio, w czasie tworzenia tego postu jest to wersja 2019. Stwórzmy nowy projekt ASP.NET Core:

Następnie wpisujemy nazwę projektu – oczywiście może być ona dowolna.

Wybierzmy teraz opcję “API” z podanych templates – można by równie dobrze użyć “Empty” (i jeśli w swoim projekcie taką wybrałeś, lub jakąkolwiek inną, to też wszystko będzie działać), lecz opcja “API” ułatwi trochę sprawę – będzie mniej pisania 🙂
Może nasunąć się pytanie – czemu nie “Angular”? Ponieważ go stworzymy osobno – oczywiście nic nie przeszkadza mieć go razem z backendem, ale jednak częściej się go robi osobno więc tak też zrobimy teraz – SignalR działa identycznie dla obu przypadków.
Po stworzeniu projektu w takiej konfiguracji, powinieneś mieć u siebie taką strukturę plików:

By SignalR działał w naszej appce, musimy dodać odpowiednią paczkę – kliknij prawym na projekt – Manage NuGet Packages…

I w polu Browse wpisujemy: Microsoft.AspNetCore.SignalR i instalujemy paczkę o takiej nazwie

I tym oto sposobem przygotowania naszego projektu ASP.NET Core są zakończone. Czas na kod!
Wchodzimy do pliku Startup.cs by włączyć nasze SignalR. W funkcji ConfigureServices dopisujemy linijkę services.AddSignalR()
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSignalR();
}
Teraz ważna uwaga – nasza aplikacja Angular będzie uruchamiana osobno – czyli będzie miała inny port niż nasze api (przykładowo: API uruchamia się na https://localhost:44331, a angular na http://localhost:4200). Domyślnie, SignalR nie będzie pozwalać na komunikację “międzyportową” – aby to umożliwić, musimy dodać CORS (Cross-Origin Requests Policy)
Dodajemy to w dwóch miejscach – pierw w ConfigureServices dodajemy:
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {
builder
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
TIP: Zamiast .WithOrigins(“https://localhost:4200”) możemy dodać .AllowAnyOrigin(), żeby nie musieć wpisywać ścieżki aplikacji front-endowej (Angulara w tym przypadku), ale niestety jest to mniej bezpieczne więc w tym poradniku zostanę przy .WithOrigins()
Oraz w Configure dodajemy:
app.UseCors("CorsPolicy");
I gotowe. Ten string “CorsPolicy” możemy sobie schować w jakieś zmiennej czy pobierać z konfiguracji czy co tam preferujesz – jest to obojętne.
Teraz zaczniemy używać tego całego SignalR – więc pozwól, że wytłumaczę co chcemy osiągnąć. By zademonstrować Ci działanie komunikacji pomiędzy API a Angularem, zrobimy sobie wyświetlanie wiadomości na Angularowej stronie, którą wyślemy z zewnątrz do API. Czyli zrobimy sobie jednego endpointa w API który przyjmie treść wiadomości, i API po jej dostaniu poinformuje Angulara o tym że ta wiadomość przyszła i ją wyśle za pomocą SignalR.
Czyli robota w projekcie ASP.NET sprowadza się do dwóch rzeczy – zrobienie endpointa z pobieraniem wiadomości, oraz tego “wysyłacza SignalR’owego”. Zaczniemy właśnie od niego – zróbmy sobie nowy interfejs o nazie np. IMessageHubClient, a w nim metoda do wysyłania tej wiadomości, np. BroadcastMessage(string message)
public interface IMessageHubClient
{
Task BroadcastMessage(string message);
}
Interfejs gotowy, to teraz czas na jego implementację. Zróbmy nową klasę o nazwie np. MessageHub. I teraz ciekawa rzecz – nie robimy w nim implementacji – nie jest potrzebna. Zamiast tego, dziedziczymy po SignalR’owej klasie Hub, której podajemy nasz stworzony interfejs, w taki oto sposób:
using Microsoft.AspNetCore.SignalR;
namespace ProtonSoftwareSignalR
{
public class MessageHub : Hub<IMessageHubClient>
{
}
}
I to tyle co do naszego Huba, teraz zróbmy ten endpoint do wysyłania wiadomości – zróbmy nowy controller np. ApiController wraz z jedną funkcją która przyjmie tą wiadomość przykładowo GETem (oczywiście nie ma to znaczenia, ale GET umożliwi nam bardzo łatwy dostęp w przeglądarce, nie trzeba będzie angażować Postmana). W tej funkcji użyjemy naszego Huba – tylko skąd go dostaniemy? A z Dependency Injection oczywiście, czyli robimy sobie kontruktor i “prosimy” o dostanie tego Huba, którego sobie przechowamy w prywatnej zmiennej. Nasz Hub będzie dostępny pod postacią SignalR’owego interfejsu IHubContext<MessageHub, IMessageHubClient>
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
namespace ProtonSoftwareSignalR
{
public class ApiController : ControllerBase
{
private readonly IHubContext<MessageHub, IMessageHubClient> mHubContext;
public ApiController(IHubContext<MessageHub, IMessageHubClient> hubContext)
{
mHubContext = hubContext;
}
}
}
Huba już mamy, to teraz skończmy nasz endpoint – nowa funkcja przyjmująca wiadomość jako string i nad nią HttpGet. I teraz jak wysłać tą wiadomość do Angulara?
mHubContext.Clients.All.BroadcastMessage(message);
Nasz Hub ma podpiętych klientów, wybieramy wszystkich, i używamy tej metody z naszego interfejsu do wysłania wiadomości. Simple as that. SignalR zajmie się resztą. Czyli nasza funkcja wygląda następująco:
[HttpGet]
[Route("send")]
public void SendMessage(string message)
{
mHubContext.Clients.All.BroadcastMessage(message);
}
Jak widać dodałem jeszcze Route’a, żeby się łatwo do tej metody dostać. Ostatnim krokiem będzie jeszcze udostępnienie scieżki dla Angulara, żeby mógł się do niej “doczepić”, i nasłuchiwać wiadomości od API. Wchodzimy do pliku Startup.cs i wklejamy kod w funkcji Configure do app.UseEndpoints, które w rezultacie będzie wyglądać tak:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<MessageHub>("/notify");
});
I to tyle z projektu ASP.NET – wszystko działa jak należy. Angular będzie mieć nasłuchiwać pod scieżką “localhost/notify”. Czas na poznanie drugiej strony mocy.
SignalR od strony Angular 8
API mamy skonfigurowane i przygotowane, możemy je odpalić w tle, a teraz czas zająć się naszym frontem. Także zaczynamy również tworząc naszą aplikację – w wybranym folderze otwieramy wiersz polecenia (cmd) i wpisujemy:
ng new AngularSignalR
Gdzie AngularSignalR to nazwa aplikacji i może być ona dowolna. Następnie Angular CLI stworzy nam aplikację, na pytanie o routing oraz style wybieramy dowolną odpowiedz – nie będzie w tym poradniku to istotne (osobiście wybrałem y dla routingu i CSS dla styli).
Aplikacja utworzona, teraz czas na dodanie paczki odpowiedzialnej za połączenie SignalR – wchodzimy do folderu aplikacji, odpalamy tam ponownie wiersz polecenia, i wpisujemy coś takiego:
npm install @aspnet/signalr --save
I tym sposobem SignalR jest dodany do naszego front-endowego projektu. Naszym celem jest wyświetlenie wiadomości, którą dostaniemy od back-endu – dodajmy ją więc w postaci zmiennej do wyświetlenia w html-u głównego komponentu, jak i w TypeScripcie. Zwykły string który będzie wyświetlony. Pliki komponentu prezentują się następująco – kolejno plik app-component.html oraz app-component.ts:
<p>Wiadomość: {{ message }}</p>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
message: string;
ngOnInit() {
}
}
To ostatnią rzeczą, która nam została jest pobranie tej wiadomości od api. Jak to zrobimy? Dwa etapy – pierwszy to nawiązanie połączenia z back-endem, wskazanie ścieżki i parametrów, tak żeby Angular nasłuchiwał zmian:
import * as signalR from '@aspnet/signalr';
.
.
.
const connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Information)
.withUrl("https://localhost:44398/notify")
.build();
A drugi to ustawianie tej wiadomości gdy ona “przyjdzie” (ten string BroadcastMessage to nazwa metody w IMessageHubClient):
connection.on("BroadcastMessage", (message: string) => {
this.message = message;
});
Ustawiamy obie te rzeczy w funkcji ngOnInit(), która to wywołuje się przy ładowaniu komponentu. Daje nam to w rezultacie taką zawartość pliku app-component.ts:
import { Component, OnInit } from '@angular/core';
import * as signalR from '@aspnet/signalr';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
message: string;
ngOnInit() {
const connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Information)
.withUrl("https://localhost:44398/notify")
.build();
connection.start().then(function () {
console.log('Połączenie nawiązane poprawnie');
}).catch(function (err) {
return console.error(err.toString());
});
connection.on("BroadcastMessage", (message: string) => {
this.message = message;
});
}
}
I to wszystko. Uruchamiamy back-end oraz front-end (Angulara odpalamy za pomocą ng build lub ng serve). Po uruchomieniu obu apek, na stronie “Angularowej” powinniśmy zobaczyć pustą wiadomość i komunikat o poprawnym połączeniu w konsoli przeglądarki

A po wpisaniu ścieżki do api wysyłającej wiadomość:
https://localhost:44398/send?message=SignalRJestProsty
Ujrzymy wręcz natychmiastowo w naszej apce Angular wysłany tekst:

To już wszystko, co potrzebujesz dla swojej aplikacji by wdrożyć poprawnie komunikację pomiędzy frontem i back-endem za pomocą SignalR. Zamiast stringa z wiadomością, równie dobrze możesz tam przesyłać wszelakie obiekty w postaci JSON’ów. Wszystko zależy od zastosowania.
Kliknij tutaj, aby zobaczyć kod źródłowy dotyczący tego postu.
Miłego kodowania i do następnego razu!
1 Comment
Bartek · 26 January 2020 at 20 h 20 min
Dzięki wielkie, świetna robota! Szukałem prostego rozwiązania z budowaniem huba 😉 U siebie zamiast front endu użyłem appki konsolowej do odbioru wiadomości. Dobry tutorial do signalR!