Frontend napisany w C#? Poznaj Blazor
Pokażę Tobie możliwą przyszłość, która spowoduje, że za kilka miesięcy ja, programista .NET, będę tworzył frontend w C# zamiast w znienawidzonym JavaScripcie. I nie mam tutaj na myśli jakiegoś przekształcania kodu z C# na JavaScript pokroju bridge.net, tylko natywną aplikację .NET działającą w przeglądarce. I to wszystko bez instalowania wtyczek i innych dodatków, oparte na standardach. Prawda, że zapowiada się ciekawie?
Daniel Plawgo. .NET Developer w SoftwareHut, Trainer, Consultant, Bloger (plawgo.pl). Od wielu lat jest związany ze społecznością Microsoft. Początkowo był Student Partnerem i prowadził grupę .NET Eastgroup na Wydziałe Matematyki i Informatyki UWM. Później był Student Consultantem odpowiedzialnym za studenckie grupy .NET z północnej Polski. Od prawie 10 lat prowadzę zawodową grupę .NET OLMUG u nas w Olsztynie.
Spis treści
WebAssembly
Możliwość wykonywania kodu C#/.NET (i w sumie w praktyce dowolnego innego języka) istnieje dzięki WebAssembly (Wasm). Jest to otwarty standard, który umożliwia skompilowanie różnych języków programowania (jak C#, Java, C++ czy wiele innych) do binarnego formatu, który będzie później wykonywany po stronie przeglądarki z prawie natywną szybkością.
WebAssembly jest wspierane przez większość popularnych przeglądarek. Według danych caniuse.com na koniec maja 2019 85,8% przeglądarek na świecie wspiera ten standard. Zaliczają się do nich Chrome, Chrome for Android, Firefox, Edge, Safari, iOS Safari. Powoduje to, że już teraz jako programista praktycznie nie muszę się martwić tym, czy moja aplikacja utworzona dzięki WebAssembly będzie dostępna dla użytkowników. Co ważne, liczba ta zmienia się z każdym miesiącem. Jeszcze w marcu 2019 standard ten był wspierany przez 81,41% przeglądarek.
Istotne jest również to, drogi Czytelniku, że WebAssembly nie zostało stworzone, aby zastąpić i wyprzeć JavaScript. Ma go raczej uzupełniać w tych miejscach, gdzie JavaScript radzi sobie trochę gorzej – na przykład w tworzeniu gier czy obsłudze multimediów, gdzie możliwość wykorzystania bibliotek z innych języków i platform może ułatwić tworzenie aplikacji.
Z drugiej strony dzięki WebAssembly możemy również przenieść technologie znane ze świata backendu do świata frontendu. Blazor jest właśnie takim frameworkiem bazującym na WebAssembly, który umożliwia wykonywanie kodu .NET po stronie przeglądarki i tworzenie aplikacji w C#.
W tym artykule skupię się na Blazorze, ale odsyłam Cię do innego artykułu dedykowanego WebAssembly.
Blazor
Microsoft skompilował specjalną wersję .NET Framework bazującą na Mono do WebAssembly, dzięki czemu możemy po stronie przeglądarki wykonywać kod .NET. W tym momencie jest to Mono, ale w momencie gdy Microsoft w 2020 roku wypuści .NET 5, Blazor będzie działał dokładnie na tym samym frameworku, co kod po stronie serwera.
W tym momencie Blazor może funkcjonować w dwóch trybach:
- Client-Side – opartym właśnie na WebAssembly, gdzie wszystko wykonuje się po stronie przeglądarki.
- Server-Side – gdzie kod wykonuje się po stronie serwera, a wszystkie zdarzenia z przeglądarki oraz wygenerowany HTML są przesyłane do przeglądarki dzięki SignalR.
W ostatnim czasie Blazor wyszedł z fazy eksperymentalnej i jest dostępny w wersji Preview. Znamy też wstępne daty premiery poszczególnych wersji. Wraz z .NET Core 3.0 we wrześniu 2019 roku ma zostać udostępniona wersja finalna działająca w trybie Server-Side. Natomiast rok później wraz z publikacją .NET 5 ma pojawić się wersja Client-Side.
W tym artykule skupię się bardziej na wersji Client-Side, która wydaje się dużo ciekawsza z racji tego, że działa w całości po stronie przeglądarki.
Utworzenie nowej aplikacji
Do utworzenia projektu Blazora najlepiej wykorzystać Visual Studio 2019. Może być to dowolna wersja, nawet wersja Community. Do tego należy również zainstalować dodatek Blazor, który dodaje wsparcie dla Blazora do Visual Studio. Dodatek może wymagać zainstalowania czegoś jeszcze, na przykład określonej wersji .NET Core.
Po zainstalowaniu wszystkich niezbędnych rzeczy możemy przystąpić do tworzenia nowego projektu. W pierwszym ekranie po uruchomieniu Visual Studio wybieramy „Create a new project”:
W kolejnym ekranie wybieramy typ projektu „ASP.NET Core Web Application”:
W kolejnym kroku określamy nazwę projektu oraz solution i wybieramy miejsce, w którym projekt ma zostać zapisany:
W ostatnim kroku wybieramy szablon projektu. W naszym przypadku będzie to właśnie Blazor działający w trybie Client-Side:
Po utworzeniu projektu solution będzie wyglądało tak:
W wygenerowanym projekcie widzimy kilka rzeczy znanych ze standardowego projektu .NET Core. Po pierwsze mamy klasę Program z metodą Main, od której startuje nasza aplikacja, i do tego klasę Startup, w której znajduje się konfiguracja aplikacji.
Z takich rzeczy specyficznych dla Blazora widzimy dwa foldery Pages oraz Shared. W pierwszym znajdują się widoki, do których później będziemy mogli nawigować. Natomiast w drugim folderze znajdują się rzeczy wspólne dla wszystkich widoków, czyli różne komponenty, które możemy osadzić na stronach, oraz plik layoutu.
Do tego w folderze wwwroot znajduje się plik index.html, który jest przesyłany do przeglądarki i który później ładuje całą aplikację.
Strona
Testowa aplikacja wygenerowana na podstawie szablonu zawiera trzy widoki. Pierwszy to Index, który jest widokiem startowym. Nie zawiera nic ciekawego, więc go pominę.
Ciekawsze rzeczy dzieją się w widoku Counter:
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" onclick="@IncrementCount">Click me</button> @functions { int currentCount = 0; void IncrementCount() { currentCount++; } }
W pierwszej linii pliku znajduje się dyrektywa page. Określa ona, pod jakim adresem będzie znajdowała się strona i co użytkownik musi wpisać w adresie, aby ją zobaczyć. W tym przypadku jest to wartość „/counter”.
Następnie w pliku widzimy HTMLa z C#. Blok @function jest to kod C# danej strony. W tym bloku znajdują się dwie rzeczy. Pole currentCount typu int oraz metoda IncrementCount, która zwiększa o jeden wartość pola.
W samym HTML-u oba te elementy z C# są wykorzystane. W paragrafie w linii 5 wyświetlamy wartość tego pola. Aby wyświetlić wartość, korzystamy z zapisu: @ i nazwa pola. Co ważne, Blazor będzie aktualizował wyświetlaną wartość w momencie zmiany wartości pola.
Natomiast metoda IncrementCount
jest podpięta pod zdarzenie onclick przycisku na stronie. W momencie jego naciśnięcia wykona się metoda, która zmieni wartość pola, i nastąpi aktualizacja widoku z nową wartością:
Drugim, jeszcze ciekawszym widokiem w testowej aplikacji jest widok FetchData.razor:
@page "/fetchdata" @inject HttpClient Http <h1>Weather forecast</h1> <p>This component demonstrates fetching data from the server.</p> @if (forecasts == null) { <p><em>Loading...</em></p> } else { <table class="table"> <thead> <tr> <th>Date</th> <th>Temp. (C)</th> <th>Temp. (F)</th> <th>Summary</th> </tr> </thead> <tbody> @foreach (var forecast in forecasts) { <tr> <td>@forecast.Date.ToShortDateString()</td> <td>@forecast.TemperatureC</td> <td>@forecast.TemperatureF</td> <td>@forecast.Summary</td> </tr> } </tbody> </table> } @functions { WeatherForecast[] forecasts; protected override async Task OnInitAsync() { forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json"); } class WeatherForecast { public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF { get; set; } public string Summary { get; set; } } }
Na początku pliku poza dyrektywą page zawiera również dyrektywę inject. Służy ona do wstrzykiwania zależności – podobnie jak to robimy w zwykłych klasach, wstrzykując zależności przez konstruktor. Blazor ma zaimplementowany prosty mechanizm Dependency Injection.
W tym przypadku wstrzykujemy instancję klasy HttpClient, za pomocą której pobierzemy dane z serwera i wyświetlimy je w tabelce w widoku.
Na końcu pliku w bloku functions znajduje się kilka rzeczy; jest to m.in. definicja klasy WeatherForecast, które będzie reprezentować dane przychodzące z serwera. Do niej zostaną zserializowane dane z jsona.
Metoda OnInitAsync jest wywoływana przez Blazora po wczytaniu widoku. W niej właśnie korzystamy z wstrzykniętej instancji klasy HttpClient i wywołujemy metodę GetJsonAsync, która wyśle asynchronicznie żądanie pod wskazany adres, pobierze jsona i go zdeserializuje – w tym przypadku do tablicy WeatherForecast – oraz przypisze do pola forecasts.
Testowa aplikacja w folderze wwwroot/sample-data zawiera statyczny plik weather.json, który jest jsonem z testowymi danymi.
Sam HTML widoku nie jest mocno skomplikowany. Na początku mamy ifa, który sprawdza, czy pole forecasts jest ustawione, czy nie. Z racji tego, że pobranie danych jest asynchroniczne, to przez chwilkę po załadowaniu widoku danych tam nie będzie. W takiej sytuacji wyświetli się komunikat „Loading…”.
W momencie pobrania danych widok wyświetla je w tabelce z wykorzystaniem pętli foreach, za pomocą której przechodzimy po tablicy z danymi i renderujemy kolejne wiersze tabeli.
W efekcie uzyskamy taki wynik:
Działanie aplikacji
Na końcu warto zobaczyć, co dzieje się pod spodem testowej aplikacji. Poniżej znajduje się zrzut ekranu z narzędzia developerskiego Chrome’a, w którym widać to, co jest przesyłane z serwera do przeglądarki.
Czerwoną ramką zaznaczyłem rzeczy specyficzne dla Blazora. Jak widać, na początku jest pobierany plik mono.js, który tak naprawdę jest odpowiedzialny za pobranie oraz start mono, które jest pobierane w kolejnym żądaniu (mono.wasm – wasm określa WebAssembly).
Następnie pobierane są kolejne pliki dll (BlazorDemo.dll to kod naszej testowej aplikacji), a później kolejne pliki frameworka.
Jak widać, samo mono zajmuje trochę miejsca. W tym przypadku jest to 909 KB, natomiast cała aplikacja zajmuje 2.4 MB. Jeśli chodzi o prostą aplikację, która praktycznie nic nie robi, to za dużo. Z drugiej strony takie frameworki jak angular też trochę ważą.
Jednym z planów Microsoftu w stosunku do Blazora w najbliższych miesiącach jest właśnie zmniejszenie liczby danych, jakie musimy przesyłać przy starcie aplikacji. Podczas kompilacji będą usuwane wszystkie zbędne elementy, aby wynikowe dll zawierały tylko ten kod, który faktycznie jest potrzebny do działania aplikacji. Ale na to musimy jeszcze trochę poczekać.
Podsumowanie
W momencie gdy pierwszy raz zobaczyłem Blazora, od razu się w nim zakochałem. Bardzo mocno kibicuję, aby ten framework odniósł sukces, i liczę na to, że za kilka miesięcy będę mógł tworzyć frontend z wykorzystaniem C#/.NET zamiast JavaScript/TypeScript.
W aktualnych czasach, gdy chcemy zbudować bardziej rozbudowaną aplikację, to tak naprawdę potrzebujemy dwóch zespołów programistów. Jedni zajmą się backendem, a drudzy frontendem. To później powoduje szereg problemów: a to problemy komunikacyjne, gdyż większa liczba osób musi się dogadać odnośnie do tego, jak ma działać system, i wcześniej czy później gdzieś nastąpi jakieś niezrozumienie. A to problemy techniczne, ponieważ musimy w jakiś sposób połączyć oba te światy. Oczywiście są rozwiązania takie jak Swagger, które dużo ułatwiają, ale zawsze to jakiś dodatkowy narzut pracy.
Z drugiej strony bycie takim full stackiem nie jest również zbyt łatwe. Szybkość rozwoju różnych technologii, w szczególności po stronie frontendu, powoduje, że trudno jest za tym wszystkim nadążyć. Do tego używanie różnych narzędzi, bibliotek i języków w obu tych światach powoduje, że naprawdę trudno być bardzo dobrym full stackiem.
Dlatego osobiście bardzo mocno kibicuję takim rozwiązaniom jak Blazor, które mogą spowodować, że tworzenie aplikacji będzie łatwiejsze. Jako programista .NET będę mógł wykorzystywać te same narzędzia, biblioteki oraz języki do tworzenia frontendu, jakich używam teraz po stronie backendu. Dzięki temu będę mógł być efektywniejszym programistą, który szybciej realizuje to, czego potrzebuje mój klient. Co bardzo mnie cieszy.
A jakie jest Twoje zdanie, drogi Czytelniku?
Zdjęcie główne artykułu pochodzi z unsplash.com.