Podsumowanie konkursu Daj się poznać 2017

Nadszedł ten dzień, trzy miesiące minęły bezpowrotnie. Konkurs Daj się poznać 2017 dobiega końca. Pora więc na małe podsumowanie i kilka refleksji. Zapraszam do lektury!

Projekt

Dla mnie ta przyjemniejsza część konkursu. Plan minimum został wykonany, gra w podstawowej wersji działa. Co prawda muszę dodać jeszcze kilka szczegółów przed jej upublicznieniem (np. większa baza słów), ale to już jest bardziej kwestia niezwiązana z programowaniem. Co mi się udało zrobić opisałem w poprzednim poście, dlatego nie będę się na ten temat rozpisywał. Czy mogłem zrobić więcej? Ciężko mi powiedzieć. Na pewno bardzo dużo czasu poświęciłem na poznanie nowych technologii. Nie obyło się także bez walki z problemami związanymi z niestabilnymi jeszcze technologiami. Wiadomo, gdybym użył znanych mi narzędzi, dużo więcej udałoby mi się zrobić. Ale nie o to mi chodziło, to właśnie o poznanie nowych rzeczy tutaj chodziło. Czy jestem zadowolony z wyników pracy? Myślę, że tak. Powiedzmy że szklanka w tym przypadku jest w połowie pełna.

Blog

To była ciężka przeprawa. Stres związany z przelewaniem swoich myśli oraz przekazywaniem ich publicznie był na początku bardzo duży. Z czasem jednak już o tym nie myślałem, więc tutaj duży plus! Podobnie było z czasem jaki musiałem poświęcać na pisanie postów. Na początku potrzebowałem na to 3-4 godziny, nie licząc oczywiście czasu na research w danym temacie. Tutaj także nabrałem wprawy, udało mi się zejść do około 2 godzin na samo pisanie artykułów. Niestety czas pracy zacząłem zapisywać dopiero pod koniec marca, dlatego nie mam dokładnego porównania jak to się zmieniało. Często zamiast blogować, miałem ochotę dalej rozwijać projekt. Ale wiadomo, trzeba się było trzymać harmonogramu i posty musiały się pojawiać.

Podsumowanie_meme

Dzisiejszy post jest dwudziestym w trakcie trwania konkursu. Minimum wykonane, udało się wytrwać, trochę jak ten mem. Na początku konkursu posty pisałem z większym entuzjazmem niż w ostatnich tygodniach. Nie wiem czy 20 postów to aż tak dużo, ale ilość ta trochę mi ciążyła. Dlatego cieszę się że konkurs dobiega już końca(prawie tak bardzo jak fakt, że wytrzymałem do finału). Posty w których opisywałem tylko to, co udało mi się zrobić w projekcie, traktowałem jako przykry obowiązek. Jednak ku mojemu zdziwieniu, pisanie tych bardziej technicznych postów sprawiało mi wiele frajdy.

Trochę liczb

Skoro już była mowa o tym, ile czasu poświęcałem na pisanie postów, podzielę się ile czasu poświęciłem na projekt. Nie są to dane całkowite, bo zacząłem je śledzić dopiero od 26 marca, więc już praktycznie po pierwszym miesiącu. Jestem pewny, że razem z danymi za cały marzec, dużo więcej godzin znalazło by się w statystykach odnośnie pisania bloga.

Statystyki 1

Łączny czas pracy 26.03 – 30.05

Jak widać, duuużo więcej czasu poświęciłem na sam projekt. Około 24h na 12 postów. W marcu proporcje były moim zdaniem odwrotne, ale tego nie mam niestety jak sprawdzić, musicie mi uwierzyć na słowo.

Statystyki 2

Rozkład czasu pracy

Jak widać, początek kwietnia to był najbardziej pracowity okres. Tutaj prosta przyczyna, miałem kilka dni wolnego. A dalej to już różnie bywało. Powiedziałbym, że pracowałem w równych dwutygodniowych cyklach, ale nie wiem z czego to wynikało. Na koniec jeszcze ogólne podsumowanie. Można powiedzieć, że udało mi się wykręcić dodatkowe pół etatu.

Statystyki 3

Podsumowanie z podziałem na miesiące

Co dalej?

Jeżeli chodzi o projekt, to na pewno doprowadzę go do końca. Sam chcę z niego korzystać. Myślę, że moi znajomi też czasem z niego skorzystają. A co dalej z blogiem? Przyznam szczerze, że spodobało mi się pisanie postów. Spotkałem się z kilkoma miłymi słowami w związku z niektórymi postami, dlatego na pewno będę starał się wrzucać tutaj coś nowego raz na jakiś czas. Oczywiście nie z taką częstotliwością jak dotychczas, a na pewno dam sobie kilka długich dni przerwy.

Jeżeli chodzi o sam konkurs, to dał mi sporego kopa motywacyjnego do pracy. Czy wezmę jednak udział za rok? Nie wydaje mi się. Wiem już, że jeżeli chce, to potrafię wytrwać i pracować z taką regularnością. Jednak praca na blogu i tak częste jego aktualizowanie to dla mnie troszkę za dużo. Dlatego za rok będę tylko obserwował poczynania innych.

Dziękuję tym, którzy czytali moje wypociny przez ten czas. Gdyby nie Wy i ta skromna, ale regularna liczba wejść na bloga nie wytrwałbym do końca! Pozdrawiam i zapraszam ponownie!

Postęp prac nad projektem – to działa!

Dzisiaj chciałem opisać w skrócie to, co udało mi się zrobić w ostatnim czasie. Jest to ostatnia aktualizacja przed końcem konkursu. Najważniejsze się udało, czyli możliwość gry dla dwóch drużyn! W najbliższym czasie postaram się przeprowadzić testy w gronie znajomych. Później udostępnię aplikację dla wszystkich. Zaczynamy!

Tryb gry i dostępne opcje

Z dwóch trybów gry, które były w założeniach, udało mi się zrobić jeden. Jest to standardowa gra dla dwóch drużyn, gdzie pierwsza, która odkryje swoje pola, wygrywa. Rozgrywka sterowana jest za pomocą jednego urządzenia, które należy do założyciela rozgrywki. Inni gracze mogą podglądać co się dzieje na swoich urządzeniach. Dzięki temu można np. odpalić grę w salonie na telewizorze. Jest także niezbędna dla kapitanów mapa, która wskazuje, które pola należą do poszczególnych drużyn.

Mapa dla kapitanów

Mapa gry z odkrytymi polami

Mapa gry jest identyczna do planszy, ale nie można na niej wykonywać żadnych operacji. Z poziomu planszy możemy zakończyć naszą turę. Na obu ekranach w górnym panelu wyświetla się informacja o tym, która drużyna wykonuje obecnie ruch.

Przycisk zakończenia tury

Zakończenie tury i zmiana drużyny

Na podstawie skojarzeń podanych przez kapitana gracze odgadują, które pola miał na myśli. W przypadku, gdy trafią na puste pole lub pole drużyny przeciwnej, ich tura dobiega końca. W przypadku, gdy odkryją własne pole, mogą kontynuować rozgrywkę. Po odkryciu wszystkich swoich pól, wygrywają. Gdy trafią na jedną z pułapek, gra dobiega końca i wygrywa przeciwny zespół. Oczywiście wszystkie akcje synchronizowane są na wszystkich podłączonych urządzeniach.

Odgadywanie pól

Odgadywanie pól i zmiana drużyny przy złym ruchu

Tym razem nie będę opisywał żadnych technicznych szczegółów. Wszystko z czego korzystałem było opisane w moich poprzednich postach w ostatnich tygodniach. Dotyczyły one takich technologii i bibliotek jak SignalR, Asp.Net Core, Angular lub Dapper.Net. Nowe technologie nie pojawiły się w ostatnim czasie, więc chyba nie ma potrzeby powielać tutaj tych informacji.

Co jest jeszcze do zrobienia?

Sporo. Tak mógłbym w skrócie napisać. Pomimo, że gra sama w sobie działa, bardzo dużo jest jeszcze do zrobienia. Tryby dla większej ilości graczy to na pewno jedna z pierwszych rzeczy które są w moich planach. Różne mniejsze poprawki które ułatwiają życie: możliwość wchodzenia do gier znajomych, social logins(Facebook, Google etc), uruchamianie nowej gry jednym kliknięciem po zakończeniu obecnej rozgrywki lub poprawki w interfejsie. To tylko część funkcjonalności, które także chciałbym żeby znalazły się w finalnej wersji. Tryb gry, gdzie każda drużyna steruje swoim urządzeniem to także coś, co chciałbym dodać. Niestety, to raczej będzie zaimplementowane w drugiej kolejności. Ostatnia rzecz to natywny klient na system android. Szczerze, to nie wiem czy ten pomysł wejdzie w życie, zobaczymy. Obawiam się, że prędzej zajmę się nowym projektem, ale kto wie!

Podsumowanie

Plan minimum został wykonany, chociaż po cichu liczyłem na więcej. Wiadomo, życie potrafi brutalnie zweryfikować to, co sobie założymy. Patrząc na całość, jestem w miarę zadowolony z tego co zostało zrobione. Za jakiś czas, już po konkursie, na pewno pochwalę się pozostałymi rzeczami które dodałem. Na razie mam nadzieję, że wśród moich znajomych znajdą się dobrzy ludzie, którzy zechcą ze mną przetestować moją grę!

WordHunt – plansza do gry przy użyciu Flex Layout

Dzisiaj pokażę jak wygląda plansza do gry. Przyznaję, że spodziewałem się że ten element zostanie skończony już dawno, ale wyszło jak wyszło. Zadanie to sprawiło mi więcej problemów niż się spodziewałem, ale jestem w miarę zadowolony z tego co otrzymałem.

Plansza i jej elementy

Jeżeli chodzi o styl, pozostawia ona jeszcze sporo do życzenia, ale pola na planszy zachowują się tak jak chciałem. Dostosowują się do wielkości ekranu, w pełni wypełniając całą dostępną przestrzeń.

Board_mobile

Plansza do gry 5×5 na małym urządzeniu

W przypadku, gdy plansza posiada więcej słów i pola nie będą się mieścić w całości, wyrazy zostaną ucięte. W przyszłości czcionka będzie zmieniać rozmiar w zależności od wielkości ekranu i długości słowa, dzięki czemu unikniemy takich sytuacji. Na troszkę większych urządzeniach np. tabletach wyświetlanie działa tak jak należy, nawet na największej możliwej planszy 8×8.

board2_tablet_8x8

Plansza do gry 8×8 na tablecie

Teraz, gdy plansza jest już w takim stanie, zaczynam dodawać pozostałe fragmenty. Na tą chwilę jest jeszcze menu boczne, gdzie będą dodatkowe informacje oraz opcje. Żeby nie marnować miejsca, jest ono ukryte. Gdy je otworzymy, menu pojawia się nad planszą.

board3_sidenav

Plansza do gry – menu boczne

Obecnie obsługuję jedynie przycisk pominięcia rundy, dzięki czemu kolejna drużyna może wykonać ruch. Jutro dodam obsługę „odsłonięcia” pola, czyli jedną z najważniejszych rzeczy do zrobienia.

Jak to wygląda pod maską?

Skoro już zademonstrowałem jak plansza wygląda, pokażę teraz jak to działa. Wygląd aplikacja zawdzięcza komponentom Angular Material Design, natomiast sam layout planszy działa na bazie Flex Layout. Obie biblioteki są w wersji beta, ale większych bugów nie znalazłem. Muszę jednak powiedzieć, że próg wejścia do tych technologi jest troszkę większy niż to, co spotyka nas przy pierwszym kontakcie z popularnym konkurentem, czyli poczciwym Bootstrapem. Nie ma jednak tragedii i coś nawet da się z tym zrobić.

Ekran gry podzielony jest na kilka elementów. Kod głównego komponentu, na którym umieszczone są wszystkie elementy wygląda następująco:

Kontener, w którym wyświetlane jest menu boczne, toolbar gdzie jest tytuł aplikacji, a będą także ikony drużyn oraz plansza do gry. Tutaj nie musiałem dużo majstrować przy stylach. Jedynie toolbar wymagał drobnych modyfikacji.

Ręcznie modyfikuję rozmiar górnego paska aplikacji, ponieważ zmieniał się on przy zmianie wielkości okna. Powodowało to, że plansza do gry odrobinę się przesuwała i wychodziła poza ekran.
W bazowej klasie ze stylami zdefiniowałem mixin, który wykorzystujemy w innych komponentach, tak jak powyżej. Dzięki temu dokładnie i w łatwy sposób sterujemy stylami w zależności od wielkości ekranu. Był on szczególnie potrzebny w przypadku planszy, ponieważ przesuwała się ona w dół o wysokość paska górnego.

Sam kod planszy jest dosyć krótki, ale powiedziałbym że intensywny. Mamy tutaj dwa layouty fxLayout: column i row. Kolumnowy sprawia, że każdy rząd zabiera odpowiednią ilość miejsca w pionie. Dzięki temu plansza wypełnia całą przestrzeń. Każdy rząd składa się z komponentu game-field, który wyświetla słowa i odpowiadać będzie za kliknięcie na element.

Wartość fxFlex, która definiuje nam jak dużo przestrzeni ma wykorzystać każdy blok, wyliczana jest w kodzie po załadowaniu danych gry. Na podstawie ilości rzędów i kolumn dodajemy odpowiednią wartość do każdego elementu. Wartość bazowa baseFlexValue, od której liczymy wielkość elementów sprawia, że otrzymamy małe odstępy pomiędzy polami, które będą zmieniały wielkość w zależności od wielkości ekranu. Gdyby było tam 100, nie mielibyśmy przerw. Można to oczywiście zaprogramować inaczej, np. poprzez margin pól gry, ale na razie zostawię to tak jak jest.

Jeżeli chodzi o stylowanie tego komponentu, trzeba było dodać obliczenie wielkości planszy z uwzględnieniem paska górnego. Bez tego plansza była przesunięta o jego wysokość.

Tutaj uwaga! calc nie przyjmuje zmiennych z SASSa, chyba że opakujemy je w nawiasy klamrowe poprzedzone hasztagiem. Trochę mi zajęło zanim na to wpadłem… Szczerze, to boję się tego ruszać, bo już raz coś zmieniłem, co sprawiło że zepsułem cały ekran gry i naprawiałem go dobrych kilka minut. Szybko nauczyłem się robić częste commity, o których zdarza mi się zapomnieć. Kodu nie jest wiele, ale jako że nie jestem mistrzem CSS, trochę mi zajęło znalezienie takiej konfiguracji.

Podsumowanie

Plansza w wersji beta gotowa, teraz dodanie obsługi wykrywania pól oraz mapy dla kapitanów. Gdy to będzie gotowe, MVP projektu będzie skończone. Mam nadzieję że przed końcem konkursu!

Pierwszy pull request, czyli dokładamy swoją cegiełkę do projektów typu open source

Ostatnio napisałem kilka postów dotyczących wykorzystania biblioteki SignalR w moim projekcie. Opisywałem tam, jak dodać obsługę SignalR po stronie klienta napisanego we frameworku Angular. W tym celu wykorzystałem bibliotekę ng2-signalr. Podczas pracy z nią zauważyłem, że autor nie dodał jednej funkcjonalności, a dokładnie możliwości wyboru kanału transportu. Zagłębiłem się więc w kod źródłowy i zacząłem działać…

Mój pierwszy pull request

Każdy kto zna stronę GitHub, wie że służy ona do przetrzymywania kodu źródłowego projektów, ale nie tylko. Możemy tam za darmo hostować strony internetowe znajdujące się tam w repozytorium. Projekty mogą być publicznie widoczne dla każdego, lub prywatne (płatna opcja). Twórcy wielu bibliotek trzymają tam swój kod. Nie tylko możemy go przeglądać, ale także dokonywać w nich zmian, oczywiście za zgodą autora. Jak już wcześniej wspomniałem, sam znalazłem się w sytuacji, gdzie potrzebowałem drobnej modyfikacji. Zrobiłem ją na szybko w kopii biblioteki lokalnie, ale stwierdziłem, że nie potrwa to za długo żeby zrobić to jak należy. Nie będę rozpisywał się nad tym co dokładnie zrobiłem. Kto jest ciekawy, niech poczyta o tym tutaj.

Pull request na stronie GitHub.com

Niektórzy być może na co dzień pracują z pull requestami w pracy. Sam do takich osób nie należę, dlatego musiałem sprawdzić samemu jak to działa. A jest to bardzo proste. Zaczynamy oczywiście od wyboru projektu w którym chcemy dokonać zmiany. Następnie klikamy na przycisk Fork.

Fork

Dzięki temu dodana zostanie nasza kopia repozytorium. To w niej będziemy dokonywać zmian. Istnieje także drugi typ takiej pracy, gdzie jesteśmy współtwórcami repozytorium. Wtedy możemy dodać nasz branch i dodać pull request do zmian zawartych w nim. Tak jak tutaj pokazuje jest jednak łatwiej, ponieważ nie musimy posiadać żadnych uprawnień do repozytorium. Na naszym profilu GitHub widać że zrobiliśmy forka repozytorium.

fork_2

Następnym krokiem będzie dokonanie odpowiednich zmian w kodzie. Można to zrobić online z poziomu GitHuba. W przypadku poprawek dokumentacji może i tak zrobilibyśmy, ale w naszym przypadku zmian dokonywać będziemy lokalnie z wykorzystaniem naszego ulubionego IDE. Ten krok pominę, bo to co zmienimy zależy od projektu. Następnie robimy commit naszych zmian. Gdy już je zapiszemy, możemy wysłać pull request, czyli dajemy informację autorowi o tym, że dokonaliśmy poprawek i są gotowe do pobrania.

pr_1

Autor sprawdza co zrobiliśmy i akceptuje zmiany, lub zwraca nam je do poprawki. Jeżeli musimy poprawić część kodu, wystarczy później ją zapisać w naszym branchu. GitHub automatycznie zaktualizuje nasz pull request. Voila, gotowe. Nasze zmiany zostały przyjęte i są dostępne dla innych.

Po co mam robić cudzy projekt?

Niektórzy być może zadają sobie takie pytanie. Wymienię moim zdaniem kilka najbardziej oczywistych. Być może nie każdego to przekona, ale warto spróbować.

Dla spokoju w projekcie. Jeżeli korzystamy z biblioteki i musimy zrobić w niej jakąś poprawkę, najszybciej będzie zrobić kopię kodu źródłowego i dodać ją do naszego projektu. Nie musi to być poprawka zrobiona najlepiej jak się da. Wiadomo, ważne żeby działało. Wadą tego rozwiązania jest to, że jeżeli biblioteka otrzyma inne poprawki, które będziemy chcieli mieć w naszym projekcie, nasza poprawka będzie musiała zostać dodana na nowo. Pozornie łatwiejsza zmiana będzie niosła ze sobą więcej pracy w przyszłości. Naturalnie możemy liczyć na to, że ktoś inny dokona tych poprawek. Wiadomo jednak że najlepiej liczyć zawsze na siebie…

Dla satysfakcji. Tutaj bardzo obiektywna sprawa, ale mi bardzo wiele radości sprawia myśl, że mogę dodać zmiany z których inni będą korzystać. Wielu programistów ma to samo, ten moment gdy coś działa® i my to dodaliśmy. Mimo wielu lat, dalej jest to dla mnie coś bardzo przyjemnego, polecam.

Dla własnych korzyści. Dodanie w CV że aktywnie działamy podczas rozwoju kilku projektów typu open source to może być wielki plus w oczach pracodawcy. Ten punkt jest głównie dla programistów z mniejszym stażem, którzy w ten sposób mogą zapełnić swoje skromne CV. Łatwo pokazać w ten sposób że potrafimy coś zrobić i wiemy więcej niż uczą tylko na studiach. Doświadczeni programiści także mogą czerpać z tego korzyści, aktywny udział w dużych projektach i nie tylko może odróżnić nas od konkurencji na rynku.

Podsumowanie

Żałuję, że dopiero teraz udało mi się zrobić mój pierwszy wkład w projekty tego typu. Następnym razem, gdy będę korzystał z biblioteki w której będzie jakiś problem, poświęcę chwilę na sprawdzenie czy poprawka mojego problemu to duży problem. Może jednak dodanie jej własnoręcznie będzie lepsze niż szukanie obejścia problemu w internecie? Zachęcam Was do tego samego, w końcu na co dzień wszyscy korzystamy z bibliotek na które ktoś inny poświęcił swój czas. Może warto poświęcić trochę naszego?

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.