SignalR w ASP.NET Core i Angular – implementacja po stronie klienta

Dzisiaj pora na kolejny post dotyczący wykorzystania SignalR w ASP.NET Core. Ostatnim razem pisałem o implementacji po stronie serwera, tym razem zajmiemy się klientem aplikacji z wykorzystaniem frameworka Angular. Poniżej zamieszam odnośniki do powiązanych postów.

Część pierwsza: SignalR w ASP.NET Core – instalacja i uruchomienie

Część druga: SignalR w ASP.NET Core i Angular – przykładowa implementacja

Połączenie z serwerem

Gdy pisałem o konfiguracji, podałem na przykładzie w jaki sposób możemy połączyć się z serwerem. W tym celu wykorzystałem tzw. resolver w Angularze. Działa on na takiej zasadzie, że zapytanie wykonane w nim wykonuje się przed załadowaniem naszego komponentu, czyli np. planszy do gry. Poniżej przedstawię trochę inny przykład, gdzie ręcznie wywołujemy funkcję odpowiedzialną za połączenie. Na początek dodajemy fabrykę, która potrafi utworzyć nam nowe połączenie.

Fabrykę wykorzystujemy tylko do nawiązania nowego połączenia. Jeżeli będę chciał wykorzystywać inne konfigurację niż standardowa, nie chcę dodawać tego kodu do klasy odpowiedzialnej za samo przetwarzanie komunikatów.

Dwustronna komunikacja

Następnie dodaję serwis, który będzie przetrzymywał otwarte połączenie z serwerem oraz rozsyłał komunikaty do komponentów w Angularze. Cały klient będzie miał tylko jedno otwarte połączenie, ponieważ otwarcie takiego połączenia jest czasochłonne. Dodatkowo nie ma sensu obciążać serwera wieloma połączeniami w ramach jednej aplikacji klienckiej. Poniżej przedstawiam taką klasę, która obsługuje dwa typy zdarzeń:

  • Połączenie klienta do serwera, inicjowane prze klienta
  • Zmiana drużyny która wykonuje ruch, inicjowane prze serwer

Posiada ona kilka funkcji, omówię krótko każdą z nich.

  • connect – odpowiada za nawiązanie połączenia w ramach klienta. Połączenie jest przechowywanie w hubie i dzielone pomiędzy wszystkie komponenty.
  • disconnect– analogicznie do funkcji powyżej, zamyka ono połączenie z serwerem.
  • subscribeToGame – zdarzenie inicjowane po stronie klienta. Za jej pomocą dołączamy do kanału obecnej gry, dzięki temu otrzymamy komunikaty powiązane z grą.
  • subscribed – zdarzenie inicjowane po tym, jak nowy klient zapisze się do naszej gry.
  • teamChanged – zdarzenie inicjowane po tym, gdy jedna z drużyn wykona ruch, który spowoduje zmianę kolejki na inną drużynę.

Wykorzystanie serwisu w aplikacji

Gdy mamy już gotowy serwis, możemy wykorzystać go w naszej aplikacji. Na początek w głównym komponencie gry wywołujemy połączenie z serwerem.

Dzięki jej wywołaniu kolejno:

  • nawiązujemy połączenie z serwerem
  • zapisujemy się do gry
  • ładujemy dane gry
  • inicjalizujemy komponenty

Przykładem komponentu wykorzystującego ten serwis może być menu boczne aplikacji. Z jego poziomu drużyna może pominąć kolejkę, co będzie skutkowało zmianą obecnie aktywnej drużyny. Dodatkowo, gdy nastąpi zmiana drużyny, będziemy mogli nasłuchiwać na takie zdarzenie i wyświetlić drużynę wykonującą obecnie ruch.

Mamy tutaj trzy elementy na które należy zwrócić uwagę:

  • initialize – tutaj komponent zgłasza chęć otrzymywania komunikatów związanych ze zmianą aktywnej drużyny. teamChanged przyjmuje funkcję, która wykona się za każdym razem, gdy takie zdarzenie nastąpi.
  • onTeamChanged – wspomniana wyżej funkcja, która wykona się po wystąpieniu zdarzenia zmiany drużyny. Tutaj wyświetlony zostanie tylko komunikat w konsoli.
  • skipRound – pominięcie tury. Nie odwołujemy się tutaj bezpośrednio do SignalR’a, ale wykonujemy zwykłe zapytanie do serwera. On później roześle informacje o zmianie aktywnej drużyny

Podsumowanie

Wydaje mi się, że w tych trzech wpisach pokazałem, jak wykorzystać w naszej aplikacji bibliotekę SignalR i tworzyć aplikacje działające w czasie rzeczywistym. Zachęcam do wypróbowania jej we własnym zakresie. Jeżeli jednak miałbym być szczery, to na chwilę obecną proponuję wykorzystanie tej biblioteki w starszej wersji frameworka, chyba że tak jak ja chcecie po prostu pobawić się z nowszą wersją frameworka .NET. Co prawda, pojawił się już ASP.NET Core Preview 1, ale nie jest to wersja stabilna. Taka pojawi się dopiero pod koniec obecnego roku.

SignalR w ASP.NET Core i Angular – przykładowa implementacja

Niedawno opublikowałem wpis na temat instalacji biblioteki SignalR w projekcie ASP.NET Core z wykorzystaniem Angulara. Dzisiaj chciałem kontynuować ten temat na przykładzie z mojego projektu. Zademonstruję działającą implementację po stronie serwera.

Link do poprzedniego wpisu: SignalR w ASP.NET Core – instalacja i uruchomienie

Konfiguracji ciąg dalszy

Chociaż konfiguracja jaką podałem w ostatnim wpisie wystarczała do działania, w moim rozwiązaniu musiałem dodać jeszcze kilka ustawień. Na pierwszy ogień poszedł CamelCasePropertyNamesContractResolver, dobrze znany wszystkim, którzy tworzyli kiedyś API w Asp.Net. Dzięki niemu, obiekty zwracane są z API w notacji CamelCase. Jest to domyślne ustawienie w .NET Core, ale jako że my mamy podpiętą bibliotekę ze starszej wersji frameworka, musimy ustawić to ręcznie. Przykładowa implementacja poniżej.

Następnie rejestrujemy nasz serializator w pliku Startup.cs.

Rozsyłanie eventów z poziomu backendu aplikacji

Wysłanie zdarzenia z serwera może odbyć w dwóch przypadkach:

  • Bezpośrednio z naszej klasy, dziedziczącej po Hub, w momencie gdy klient wywołuje zdalną metodę na serwerze
  • Po wywołaniu zwykłej metody HTTP w API, pośrednio poprzez Hub

O ile w pierwszym przypadku jest to bardzo proste, to ten drugi wymaga dodatkowego nakładu pracy. Załóżmy że nasz Hub ma tylko jedną metodę.

Klient, w momencie gdy wywoła metodę Subscribe, zapisze się do grupy odpowiadającej identyfikatorowi gry. Zwrócone mu zostanie id jego połączenia. Wewnątrz klasy Broadcaster możemy wysłać zdarzenie o nowej subskrypcji do wszystkich klientów. Taki przykład podałem w poprzednim poście, dlatego tutaj to pomijam (nie jest to potrzebne w moim przypadku). Efekt który chcemy tutaj osiągnąć to wysłanie do każdego klienta informacje o tym, że gracz wykonał ruch. Jako przykład posłuży tutaj zakończenie tury przez gracza.

Podczas wywołania tej akcji kontrolera, wywołujemy metodę SkipRound. Wewnątrz niej, po tym jak nastąpi zmiana kolejki, chcielibyśmy poinformować wszystkich graczy o tym fakcie. Nasz Hub to nie jest zwykła klasa, którą można po prostu wstrzyknąć do naszych obiektów. W tym celu musimy odwołać się do klasy ConnectionManager dostarczonej przez SignalR.

Dodałem klasę EventBroadcaster, która będzie odpowiedzialna za przekazywanie informacji do klientów bezpośrednio z serwera. Posiada ona obiekt HubContext, który umożliwia takie działania. Dostępny jest po wywołaniu metody GlobalHost.ConnectionManager.GetHubContext. Ponieważ jej wywołanie jest dosyć czasochłonne, klasę EventBroadcaster rejestrujemy w systemie jako singleton.

Dzięki temu, w dowolne miejsce aplikacji możemy dodać obiekt implementujący IEventBroadcaster i wysyłać do klientów interesujące nas zdarzenia. Przykład poniżej.

Podsumowanie

Dzisiaj zademonstrowałem przykładową implementację rozsyłania zdarzeń w aplikacji po stronie serwera aplikacji. W kolejnym wpisie zajmiemy się implementacją po stronie klienta w Angularze. Dzięki za uwagę i zapraszam ponownie!

Co nowego w projekcie – aktualizacja

Od ostatniej aktualizacji minęły prawie dwa tygodnie, więc pora na krótkie podsumowanie tego, co udało mi się zrobić przez ten czas. Niestety dla projektu, przez dwa ostatnie weekendy byłem poza domem, więc nie wszystko co sobie zaplanowałem zostało zrealizowane. Jakieś postępy jednak są, dlatego zapraszam do lektury!

post-background

Exception handling w WebAPI

Przechwytywanie błędów to było coś, co mi się nie podobało w moim rozwiązaniu. Wiadomo, na początku nie było to istotne, ale w miarę jak ilość elementów aplikacji rosła, trzeba było pomyśleć nad czymś sensownym. Domyślnie, gdy w aplikacji pojawiał się błąd, wyświetlana była strona z jego treścią, standardowe zachowanie. Jako, że w API nie jest to pożądane, dodałem do aplikacji middleware który przechwytuje wszystkie wyjątki. W ten sposób nie trzeba się bawić w przekazywanie statusów z metod domeny do kontrolera – gdy trzeba rzucamy wyjątek odpowiedniego typu, a resztą zajmie się właśnie ten fragment kodu.

Teraz tylko dodajemy tą klasę do pipeline w ASP poprzez metodę UseMiddleware<ExceptionHandlingMiddleware>() i gotowe. Wszystkie wyjątki zostaną przechwycone i zwrócone do klienta. W zależności od rodzaju wyjątku, zwrócony zostanie inny status błędu.

Obsługa SignalR

Mój poprzedni post jest w całości poświęcony tej tematyce, a w szczególności wspominam tam o wszystkich problemach jakie mnie spotkały podczas dodawania tej biblioteki. Warto jednak o tym tutaj wspomnieć. Dzięki temu moja aplikacja będzie mogła działać w czasie rzeczywistym, rozsyłając komunikaty pomiędzy wszystkich klientów. O tym, jak dokładnie zostało to zaimplementowane po stronie klienta i serwera opowiem w kolejnych postach.

Praca nad widokiem aplikacji

W miarę jak dodaje kolejne elementy, powoli buduje interfejs użytkownika. Na razie ten element wyraźnie kuleje, ale nie jest to najistotniejsze w tym momencie. Wygląd na tą chwilę nie jest istotny, ważne żeby dokładać kolejne elementy funkcjonalne. Obecnie pracuje nad planszą do gry. Jest to dosyć problematyczne zadanie, ponieważ staram się żeby była możliwość gry na tabletach i smartfonach. Ciężko pomieścić tak wiele pól na małych ekranach tak, żeby były one czytelne. Do tego CSSy to nie jest moja mocna strona… Na razie poprawki tego fragmentu zostawiam na później. Nie będę się także chwalił obecnym wyglądem aplikacji, myślę że w przyszłym tygodniu coś już pokażę.

Dalsza praca z Dapperem

Tej technologii też poświęciłem już cały wpis, ale chciałem pokazać kolejną metodę którą udostępnia. Jest to QueryMultipleAsync. Tak bardzo mi się to spodobało, że postanowiłem o tym tutaj wspomnieć. Po wejściu na planszę klient chce pobrać informacje o obecnej grze: pola, status, drużyny etc. W tym celu odpytuje API. Zamiast wielu zapytań do bazy danych, całą tą operację da się zrobić w jednym zapytaniu. Można by to wydzielić na wiele mniejszych zapytań, ale stwierdziłem że to dobry moment na wykorzystanie tej metody. Poniżej implementacja.

AccessQueries w powyższym przykładzie to po prostu treści zapytań SQL. Z wielu małych zapytań generowane jest jedno wielkie. Wyniki są mapowane do poszczególnych elementów obiektu Game. W SQL Server Profilerze wyraźnie widać, wszystko wykonane w jednej operacji, bardzo miła sprawa.

Sql Server Profiler - wynik zapytania QueryMultipleAsync

Zrzut ekranu z SQL Server Profiler

Podsumowanie

Podkręciłem ostatnio tempo pracy nad projektem, dodatkowo zapowiada mi się pierwszy wolny weekend w tym miesiącu, dlatego mam spore oczekiwania względem tego, co uda mi się zrobić w najbliższych dniach! Stay tuned!

SignalR w ASP.NET Core – instalacja i uruchomienie

Dzisiaj chciałem podzielić się moimi doświadczeniami z użyciem biblioteki SignalR w projekcie .NET Core. Co prawda, nie mam jeszcze za dużo przykładów użycia w projekcie, ponieważ dopiero udało mi się zmusić ją do działania. Wydaje mi się jednak, że to co tutaj opiszę przyda się komuś, jeżeli napotka na podobne problemy co ja.

signalr sign and pretty tree

Co to jest SignalR?

SignalR to biblioteka pozwalająca na tworzenie aplikacji aktualizujących dane w czasie rzeczywistym. Co to znaczy? W standardowym scenariuszu, po uruchomieniu strony czy aplikacji pobierane są dane. Jeżeli chcemy je odświeżyć, musimy przeładować stronę lub wywołać jakąś akcję która odświeży dane (np. ponowne przefiltrowanie listy). W taki czy inny sposób wysyłamy żądanie na serwer i otrzymujemy od niego odpowiedź. Tutaj mamy do czynienia z czymś innym. To serwer informuje klienta o tym, że dane zostały zmienione. Dzięki temu natychmiast możemy mieć dostęp do interesujących nas informacji. W tym celu stosowane jest na przykład API WebSockets, czyli komunikacja dwukierunkowa klient-serwer. Do naszej dyspozycji jest także HTML5 Server-Sent Events. Tutaj komunikacja jest tylko jednokierunkowa, z serwera do klienta. Bywa jednak, że przeglądarki nie wspierają obu tych rozwiązań. Wtedy z pomocą przychodzi SignalR. Korzystając z niego nie interesuje nas w jaki sposób przebiegać będzie komunikacja, framework zajmie się tym za nas. Jeżeli tak jak wspomniałem przed chwilą, przeglądarka nie dostarcza nam tych rozwiązań, wykonane zostaną po prostu zwykłe zapytania. Ale to już jest dla programisty mało ważne, stanie się to automatycznie. To tyle w skrócie, nie są to technologie nowe, dlatego po więcej szczegółów zachęcam do dalszej lektury we własnym zakresie.

SignalR w .NET Core

Na początek pokaże, co zrobić żeby wszystko ładnie działało po stronie API naszej aplikacji. Największym problemem na jaki napotkałem był brak implementacji biblioteki pod .NET Core. Jest ona jeszcze w przygotowaniu, i żeby mieć tą wersję, trzeba ją pobrać ze specjalnego źródła, gdzie wrzucane są wersje dopiero rozwijane. Stwierdziłem, że nie będę się katował wersjami testowymi tej technologi. Sami autorzy tego nie zalecają. Do tego musiałbym podnieść wersję samego .NET Core, a tego nie chciałem robić. Wystarczą mi problemy, które mam z wersją stabilną… Jako, że byłem zmotywowany do użycia tej technologi i nie chciałem po prostu odpytywać serwera co chwilę o to, czy pozostali gracze nie wykonali ruchu, znalazłem inne rozwiązanie. Postanowiłem, że użyję frameworka .NET w wersji 4.6.2. Nie wiązało się to z przepisywaniem aplikacji od nowa. Moja aplikacja dalej pisana jest w .NET Core, ale mam możliwość korzystania ze sprawdzonych bibliotek poprzedniej wersji .NET. Wystarczy zmienić trochę plik .csproj naszej aplikacji.

Zamiast netcoreapp1.1, jako TargetFramework podajemy net462. Reszta aplikacji pozostaje w zasadzie bez zmian. Dzięki temu, dalej korzystamy z nowego .NET Core, ale możemy dodać zależności do sprawdzonego Microsoft.AspNet.SignalR.Core. W ten sposób omijamy pierwszą przeszkodę, czyli wersję biblioteki. To jednak nie wszystko.
Żeby móc podpiąć SignalR do pipeline’u naszej aplikacji, musimy dodać następujące extension methods.

Kluczowe tutaj było dodanie do pipeline UseAesDataProtectorProvider. Bez tego, SignalR działał lokalnie, ale już w środowisku produkcyjnym nie chciał nawiązać połączenia. Objawiało się to błędem Error during negotiation request.... Po wykonaniu tych czynności możemy dodać SignalR do naszej aplikacji!

A poniżej pierwszy Hub, którym testowałem, czy komunikacja działa. Nie robi nic innego, niż tylko przekazanie wszystkim połączonym klientom otrzymanej wiadomości.

SignalR w Angular

Kolejne problemy, na szczęście mniejsze, pojawiły się po stronie frontendu aplikacji. Jak zwykle jakąś rolę odegrał tutaj Webpack, ale do tego już się przyzwyczaiłem. Do komunikacji z serwerem użyłem biblioteki ng2-signalr, która dostarcza implementacje pod Angulara. Problem polegał na tym, że nie widziała ona dodanych do projektu bibliotek jQuery oraz signalr, które są konieczne do jej działania. Do obejścia tego problemu w pliku webpack.config.js należało dodać nowy plugin, który automatycznie ładuje moduły do wybranego identyfikatora. Dodatkowo w app.module.ts należało dodać importy tych bibliotek. Dzięki temu problem zniknął.

Teraz można przejść do właściwej implementacji naszego rozwiązania. Dodajemy resolver połączenia do SignalR, który już na starcie komponentu zwraca nam obiekt połączenia. W obecnej implementacji, zawsze łączy się do tego samego Huba.

Połączenie tworzone jest na podstawie domyślnej konfiguracji, która jest podawana podczas deklaracji modułu SingalR. Oto implementacja funkcji, podającej domyślną konfigurację.

Korzystam z tego samego mechanizmu, który wykorzystałem do konfiguracji całej aplikacji. Inne połączenie stosowane jest lokalnie, a inne na serwerze. Taką domyślną konfigurację przekazujemy w pliku app.module.ts.

Do funkcji connect obiektu SingalR w naszym resolverze możemy przekazać dodatkowe opcje, które nadpiszą te domyślne. W ten sposób możemy przekazać na przykład tylko nazwę Huba, do którego chcemy się połączyć. Serwer pobrany zostanie wtedy z domyślnej konfiguracji, jest to bardzo wygodne. W samym komponencie możemy teraz dostać obiekt połączenia do serwera w funkcji ngOnInit, o ile w deklaracji routingu dodaliśmy resolve przy pomocy wcześniej zaimplementowanego resolvera.

Podsumowanie

Na tą chwilę w aplikacji udało mi się zrobić tylko tyle w kwestii SignalR’a. Teraz mogę przejść do konkretnej implementacji mojego rozwiązania. Na pewno w kolejnych postach podzielę się jak ona wygląda.