Co nowego w .NET 6 dla Blazora?

Za nami konferencja .NET Conf 2021 na której został zaprezentowany .NET 6 i C# 10 wraz z usprawnieniami dla poszczególnych technologii. Nie mogło oczywiście zabraknąć tam Blazora, który jest mocno promowany przez Microsoft. Mieliśmy możliwość obejrzeć aż 13 wystąpień dotyczących Blazora. Jeżeli z jakiegoś powodu przegapiłeś je to masz jeszcze szanse zobaczyć je na kanale dotNET, szczególnie polecam prezentacje Enterprise-grade Blazor apps with .NET 6 na której zostały przedstawione nowości w Blazorze wspierające pisanie aplikacji typu Enterprise.

Blazor stał się jedną z ważniejszych technologii udostępnianych przez Microsoft, a co za tym idzie każda wersja wprowadza wiele nowości, które znacząco usprawniają pisanie aplikacji w Blazorze. Abyś się w nich nie pogubił przygotowałem wpis z podsumowaniem najważniejszych zmian w .NET 6 dla Blazora. Gotowy? To zaczynamy!

Minimal API

Pierwszą nowością jest Minimal API, czyli uproszczone tworzenie aplikacji ASP.NET Core poprzez przeniesienie konfiguracji ze Startup bezpośrednio do Program.cs, dodatkowo dzięki top-level-statements z C# 9 struktura pliku rozruchowego naszej aplikacji wygląda jeszcze bardziej czytelnie. Ten feature ma wpływ nie tylko na strukturę projektu Web API, lecz również na frontend napisany w Blazor Server.

Oto jak wygląda Program.cs dla aplikacji Blazor Server dostępny z template w Visual Studio 2022:

using BlazorServerApp.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

Zmienił się również Program.cs w przypadku aplikacji Blazor WebAssembly, oto jako wygląda Program.cs z template w Visual Studio 2022:

using BlazorWebAssemblyApp;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

Zmiana struktury pliku Program.cs redukuje niezbędny kod, który trzeba napisać, aby utworzyć aplikacje w C#. Dzięki wprowadzonym usprawnieniom podstawowa aplikacja webowa będzie mogła zostać zredukowana nawet do jednego pliku. Kierunek tych zmian sugeruje, że Microsoft chce przybliżyć się do tego jak piszemy aplikacje w innych językach takich jak Python czy JavaScript. W mojej opinii jest to krok w dobrą stronę, który uprości czytanie kodu na wszelkiego rodzaju prezentacjach czy w repozytoriach.

Więcej znajdziesz na:

Poprawa wydajności

Jest to zmiana na którą czekałem w szczególności. Aplikacje napisane w Blazorze do tej pory nie były zbyt szybkie, szczególnie, gdy porównywaliśmy je z odpowiednikami w Angularze czy Reacie (benchmark). Dla nas jako developerów oznacza to tyle, że spędzić dodatkowy czas na analizie wydajności i optymalizacji podczas projektowania aplikacji. Jeżeli i Ty zmagasz się z problemami wydajności to zachęcam do przeczytania artykułu o dobrych praktykach dotyczących wydajności w Blazorze zaproponowanych przez Microsoft.

Minie jeszcze trochę czasu zanim powstaną dokładne benchmarki z różnicami w wydajności, lecz czysto subiektywnie muszę przyznać, że już po aktualizacji do .NET 6 da się odczuć różnice. Jedno ważniejszych usprawnień dotyczy zredukowania wielkości runtime dotneta poprzez odczepianie tych części, które nie są używane. Dzięki temu wielkość podstawowej aplikacji Blazor została zredukowana z 1.7 MB do 1.1 MB, czyli aż o 35%.

Kolejnym usprawnieniem jest wprowadzenie AOT (Ahead of time compilation) dla Blazora, dzięki czemu możemy skompilować naszą aplikacje bezpośrednio do WebAssembly. Według danych z artykułu ASP.NET Core updates in .NET 6 Preview 4 aplikacja wykorzystująca AOT będzie mogła działać do 5 razy szybciej!

Ma to swoje konsekwencje, wielkość aplikacje wykorzystujących AOT jest odczuwalnie większa. Aby odblokować AOT należy w csproj dodać RunAOTCompilation.

<RunAOTCompilation>true</RunAOTCompilation>

Wprowadzone zmiany są niezbędne, aby móc traktować aplikacje Blazorowe jako alternatywa dla frontendu w technologiach JavaScriptowych przynajmniej w zespołach, które chcą utrzymywać stack technologiczny w rozwiązaniach Microsoftu.

Więcej znajdziesz na:

Hot Reload

Wsparcie dla Hot Reload jest dostępne obecnie w większości powszechnie używanych technologii, więc nie powinno dziwić, że Microsoft postanowił zapewnić je również w Blazorze. Polega ono na możliwości podglądu wprowadzonych zmian bez potrzeby przebudowywania całej aplikacji.

Przykładowe zastosowanie mechanizmu Hot Reload:

Przycisk pozwalający na wykorzystanie Hot Reload znajduje się obok uruchamiania aplikacji. Warto wykorzystać opcje Hot Reload on File Save, aby zmiany zostały wprowadzane wraz z zapisaniem pliku.

Jest to pierwsza wersja Hot Reload dla Blazora, więc ma pewne limitacje, nie wszystkie zmiany będą mogły być wprowadzone bez restartu aplikacji oraz nie jest możliwe debugowanie aplikacji Blazor WebAssembly i korzystanie z Hot Reload. Aby móc wykorzystać ten feature w aplikacjach Blazor WebAssembly należy uruchomić je w trybie bez debugowania. Jest to kolejny krok w strone poprawy produktywności w tworzeniu aplikacji Blazorowych, mam nadzieję, że zgodnie z zapowiedziami pozostałe niedociągnięcia zostaną wkrótce poprawione.

Więcej znajdziesz na:

Modyfikowanie nagłówka strony

Od .NET 6 mamy możliwość modyfikowania sekcji <head> w naszych aplikacjach Blazorowych, co powinno zapobiec tworzeniu obejść poprzez kod JavaScript i znacząco uprościć sam proces. Najczęstszych użyciem które mogę sobie wyobrazić jest modyfikowanie nagłówka strony, który pojawia się na pasku przeglądarki, ale możliwe jest również dodawanie metadanych czy obsługa przypadku nieodnalezienia strony.

Jeżeli zaktualizowaliśmy istniejąca aplikacje Blazor WebAssembly do .NET 6, to w Program.cs do RootComponents musimy dodać komponent o nazwie HeadOutlet.

builder.RootComponents.Add<HeadOutlet>("head::after");

Natomiast w aplikacjach Blazor Server należy dodać komponent HeadOutlet na końcu sekcji head.

<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />

Teraz w każdym komponencie aplikacji powinniśmy móc użyć wbudowanych komponentów takich jak <HeadConent> oraz <PageTitle>.

Przykład, który znajduje się w domyślnym szablonie projektu Blazor WebAssembly:

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

Myślę, że jest to kolejne małe usprawnienie, które może przyczynić się do przyjemniejszego pisania aplikacji w Blazorze oraz zredukować ilość niepotrzebnego kodu, aby wykonać tak proste zadania jak ustawienie nagłówka strony.

Więcej znajdziesz na:

Rozszerzenie wsparcia dla JavaScript

Sama obsługa JavaScript w Blazorze została również poprawiona. Mamy możliwość przygotowania inicjalizacji kodu JavaScript dla naszego modułu, która będzie uruchomiona przed kodem aplikacji Blazor. Dzięki temu będziemy mogli zainicjalizować biblioteki JavaScript czy nawet zmodyfikować sposób w jaki ładuje się nasza aplikacja.

Aby uzyskać dostęp do funkcji odpowiedzialnych za inicjalizacje należy w projekcie utworzyć plik {NAZWA}.lib.module.js w katalogu wwwroot, a następnie dodać tam funkcje beforeStart oraz afterStarted, które będą odpowiadać za akcje przed i po wystartowaniu aplikacji. Przykładowy plik:

export function beforeStart(options, extensions) {
    console.log("beforeStart");
}

export function afterStarted(blazor) {
    console.log("afterStarted");
}

Nie jest to koniec usprawnień, wraz z .NET 6 dostaliśmy izolacje JavaScriptu na poziomie komponentu, podobnie jak w przypadku plików ze stylami css. Oznacza to, że po umieszczeniu pliku z kodem JavaScript z nazwą {NAZWA_KOMPONENTU}.razor.js funkcje napisane w nim będą dostępne wyłącznie dla tego komponentu. Jest to dobre podejście, aby nie zanieczyszczać całej aplikacji funkcjami, które będą dostępne z każdego miejsca. Myślę, że z tych nowości najbardziej ucieszą się twórcy wszelkiego rodzaju bibliotek dostarczających interfejsy bibliotek JavaScript w Blazorze.

Więcej znajdziesz na:

Dynamiczne komponenty

W większości interfejsów, które budujemy powstaje potrzeba, aby użytkownik mógł mieć wpływ na wygląd profilu bądź dashboardu, czasami też w zależności od modelu jaki przekazujemy chcielibyśmy mieć różne widoki, a ich opis przechowywać w postaci pliku z konfiguracją. Możemy wykonać takie zadanie poprzez wprowadzenie warunków z flagami czy wyświetlić dany komponent, lecz przy większej liczbie kombinacji może być bardzo trudno utrzymywać taki kod. Od .NET 6 mamy możliwość wykorzystania innego podejścia, czyli dynamicznie renderowanych komponentów wykorzystując DynamicComponent.

Oto przykład komponentu budującego dashboard:

@page "/dashboard"

@inject IUserProvider UserProvider
@inject IWdigetProvider WidgetProvider

<h1>Dashboard</h1>

@if (widgets is not null)
{
    foreach(var widget in widgets)
    {
        <DynamicComponent Type="@widget.Type" Parameters="@widget.Parameters" />
    }
}

@code {
    private IList<IWdiget> widgets;

    protected override Task OnInitializedAsync()
    {
        widgets = WidgetProvider.GetWidgetsForUser(userProvider.GetCurrentUserId());
        return base.OnInitializedAsync();
    }
}

Na podstawie listy z obiektami typu IWidget, budowane są komponenty. Jako parametr Type należy przekazać typ komponentu jaki chcemy zbudować natomiast Parameters jest IDictionary<string, object> z parametrami, które zostaną przekazane do komponentu w momencie jego budowania. Myślę, że jest to krok w dobrą stronę, który usprawni budowanie dynamicznych interfejsów.

Więcej znajdziesz na:

Usprawnienia dla generycznych komponentów

Zmiany w działaniu generycznych komponentów również są czymś na co bardzo czekałem. Jeszcze przed .NET 6 mimo, że mogliśmy stworzyć komponent generyczny z wykorzystaniem atrybutu @typeparam to nie można było wprowadzić ograniczeń znanych z klas generycznych w C#. Powodowało to, że musieliśmy jawnie rzutować parametr przekazany z typem typeparam na interfejs bądź konkretny typ, a co się z tym wiązało nic nie chroniło nas przed napisaniem kodu, który tego rzutowania nie przejdzie i wystąpi wyjątek w czasie działania aplikacji. Na szczęście w .NET 6 zostały wprowadzone ograniczenia, oto przykład:

@typeparam TItem where TItem : ICell

Zostało także usprawnione wnioskowanie o typie na podstawie rodzica. Oznacza to, że podaniu typu w typeparam nie musimy w komponentach podrzędnych ponownie podawać go, kompilator będzie w stanie sam odgadnąć go wykorzystując do tego komponent nadrzędny. Przykład z komponentami TableTemplate oraz RowTemplate:

<TableTemplate Items="pets" TItem="Pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

Przed zmianami z .NET 6, jeżeli chcieliśmy stworzyć zarówno generyczny TableTemplate oraz RowTemplate, który otrzymałby parametr od swojego rodzica to w obu komponentach należałoby ustawić jakiego typu będzie parametr przy pomocy TItem, teraz wystarczy, że jest to ustawione w komponencie rodzica. W zależności od tego jak często wykorzystujecie komponenty generyczne to może być jedna z ważniejszych zmian z waszej perspektywy, jak dla mnie zwiększa to czytelność oraz pozbawia potrzeby jawnego ustawiania typu w wielu miejscach.

Więcej znajdziesz na:

Pozostałe nowości

To nie koniec zmian w .NET 6 dla Blazora, zostały również dodane takie elementy jak:

  • Wbudowany komponent ErrorBoundary do ograniczania propagacji błędu, dzięki możliwe jest wyświetlenie informacji o błędzie w obrębie danego komponentu,
  • Atrybut [EditorRequired] dla parametrów, aby wymuszać ustawienie go przy tworzeniu komponentu,
  • Ładowanie natywnych zależności w Blazor WebAssembly, dzięki czemu możemy na przykład korzystać z bazt Sqlite po stronie przeglądarki,
  • Rejestrowanie komponentów jako niezależne komponenty przy pomocy RegisterAsCustomElement i wykorzystywanie ich w aplikacjach Angular czy React,
  • Wbudowany komponent PersistentComponentState, który pozwala na przechowywanie stanu aplikacji i odtwarzanie go podczas inicjalizacji, aby pomijać wielokrotne pobieranie tych samych danych na starcie,
  • Poprawiono wsparcie dla SVG.

Są to także interesujące nowości, lecz ze względu na długość artykułu musze je zostawić Tobie do samodzielnego odkrycia, do czego gorąco zachęcam! Wszystkie nowości w .NET 6 znajdziesz tutaj.

Podsumowanie

Uff! Wyszło tego bardzo dużo, jeżeli udało Ci się doczytać do tego momentu to daj mi znać w mediach społecznościowych bądź poprzez wiadomość email. Podsumowując zmiany dla aplikacji Blazor wprowadzone wraz z .NET 6 wpisują się w proces zapoczątkowany już w wersji .NET 5, czyli zwiększanie produktywności developerów poprzez redukowanie zbędnego kodu oraz dawanie narzędzi, które pozwolą owocniej wykorzystywać czas podczas pisania aplikacji. Dodatkowo zostały poprawione niedociągnięcia, które występowały w Blazorze takie jak brak ograniczeń w komponentach generycznych czy brak możliwość tworzenia dynamicznych komponentów. Mam nadzieję, że Microsoft dalej będzie podążał tą drogą i w .NET 7 zobaczymy usprawnienia minimum tak dobre jak w .NET 6.

Daj mi znać co Ty uważasz za najlepsze usprawnienie w .NET 6!

Jeżeli uznasz ten post za przydatny to proszę udostępnij go innym. Zapraszam również do mojego newslettera na którym regularnie udostępniam przedpremierowo artykuły, materiały wideo i wiele więcej.

Share via
Copy link
Powered by Social Snap