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!


Patryk Mikulski

Programowaniem zajmuje się od 4 lat i jest to moja pasja. Jestem autorem wszystkiego co związane jest z ProtonSoftware.NET. Specjalizuję się w platformie .NET z językiem C# na czele. Pracuję zarówno na etacie, jak i zajmuję się własnymi aplikacjami. Najlepiej czuję się w roli team leadera. Preferuję pełne skupienie na praktyce i efektach pracy - teoria jest dobra tylko w teorii.

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!

Leave a Reply

Your email address will not be published. Required fields are marked *

en_GBEnglish
en_GBEnglish