Wprowadzenie
Ostatnio pokazałem, w jaki sposób za pomocą prerenderowania aplikacji po stronie serwera przyspieszyć ładowanie aplikacji, dzięki czemu użytkownik szybciej widzi interfejs użytkownik w przeglądarce. W tym wpisie pokażę Ci inną technikę, która dodatkowo zwiększa szybkość startu aplikacji.
Domyślnie Blazor pobiera do przeglądarki całą aplikację, wszystkie powiązane assembly, co w przypadku rozbudowanej aplikacji może okazać się niepotrzebnym narzutem. Szczególnie gdy użytkownik z racji braku uprawnień nie ma dostępu do jakiejś części aplikacji. Od jakiegoś czasu w Blazorze jest możliwość konfiguracji mechanizmu Lazy Loading Assembly, która umożliwia pobranie modułu dopiero w momencie jego pierwszego użycia.
Używanie Razor Components
W bardziej rozbudowanej aplikacji warto zastanowić się, czy nie rozbić jej na kilka modułów. Da nam to kilka korzyści. Po pierwsze mamy lepiej zorganizowany kod, a po drugie w Blazorze możemy skonfigurować lazy loading takich modułów.
Zanim przejdziemy do lazy loading, zobaczmy, w jaki sposób utworzyć aplikację składającą się z modułów. W przykładzie utworzę prosty moduł Users, który będzie zawierał jeden dodatkowy widok dla listy użytkowników (samego widoku nie będę implementował).
W tym celu dodajemy nowy projekt z szablonu Razor Class Library o nazwie BlazorLazyLoading.Client.Users. Następnie z niego wyrzucamy wszystkie pliki i foldery poza _Imports.razor.
Moduł Users będzie zawiera jedną stronę symulującą listę użytkowników. Zawartość strony będzie taka:
@page "/users" | |
<h3>Users</h3> | |
<p>This component display the users list.</p> | |
@code { | |
} |
Do modułu dodam jeszcze klasę UsersModule. W realnej aplikacji zawierałaby ona jakąś konfigurację modułu, natomiast w tym przykładzie posłuży nam jako odniesienie do assembly podczas ładowania modułów.
public class UsersModule | |
{ | |
} |
W efekcie w solution zobaczymy coś takiego:
Użycie modułu w głównej aplikacji
Mając już przygotowany moduł, możemy użyć go w głównej aplikacji. W tym celu na początku dodajemy referencje w BlazorLazyLoading.Client to BlazorLazyLoading.Client.Users.
W pliku NavMenu.razor z folderu Shared, który jest odpowiedzialny za wyświetlenie menu, dodajemy nową pozycję na liście (element dodajemy na koniec elementu ul):
Ostatnim krokiem, jaki musimy zrobić, są zmiany w App.razor. Jest to główny komponent aplikacji, w którym znajduje się przede wszystkim Router. Musimy go poinformować, że w naszej aplikacji znajdują się dodatkowe assembly ze stronami.
W tym celu tworzymy lokalne pole z listą dodatkowych assembly (linijka 14). Tutaj właśnie wykorzystuję klasę UsersModule jako odniesienie do assembly. Później podpinamy tą listę do właściwości AdditionalAssemblies komponentu Router (linijka 3). Cały plik App.razor wygląda tak:
@using System.Reflection | |
<Router AppAssembly="@typeof(Program).Assembly" | |
AdditionalAssemblies="@assemblies"> | |
<Found Context="routeData"> | |
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |
</Found> | |
<NotFound> | |
<LayoutView Layout="@typeof(MainLayout)"> | |
<p>Sorry, there's nothing at this address.</p> | |
</LayoutView> | |
</NotFound> | |
</Router> | |
@code { | |
private List<Assembly> assemblies = new List<Assembly>() | |
{ | |
typeof(Users.UsersModule).Assembly | |
}; | |
} |
Po tych zmianach możemy cieszyć się wyświetleniem strony /users z dodatkowego projektu:
Lazy Loading modułu
W tym momencie moduł Users jest pobierany do przeglądarki wraz ze startem aplikacji. Przyszedł moment, aby załadować go w momencie faktycznego wyświetlenia strony.
Pierwszym krokiem, jaki zrobimy, jest oznaczenie modułu jako assembly, który będzie wczytywany przez mechanizm Lazy Loading Blazora. W tym celu przechodzimy do pliku csproj głównego projektu (w przykładzie BlazorLazyLoading.Client.csproj), a następnie dodajemy nowy ItemGroup:
<ItemGroup> | |
<BlazorWebAssemblyLazyLoad Include="BlazorLazyLoading.Client.Users.dll" /> | |
</ItemGroup> |
Zamiana ta spowoduje, że w pliku blazor.boot.json zawierającym definicję startu aplikacji, assembly modułu Users jest oznaczone jako Lazy Loading:
Drugą zmianą, jaką musimy zrobić, jest załadowanie assembly w momencie przejścia do określonej strony. Zrobimy to w pliku App.razor, który już wcześniej modyfikowaliśmy. Jego docelowa zawartość to:
@using System.Reflection | |
@using Microsoft.AspNetCore.Components.Routing | |
@using Microsoft.AspNetCore.Components.WebAssembly.Services | |
@inject LazyAssemblyLoader assemblyLoader | |
<Router AppAssembly="@typeof(Program).Assembly" | |
AdditionalAssemblies="@assemblies" | |
OnNavigateAsync="@OnNavigateAsync"> | |
<Navigating> | |
<div style="padding:20px;background-color:blue;color:white"> | |
<p>Loading the requested page…</p> | |
</div> | |
</Navigating> | |
<Found Context="routeData"> | |
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |
</Found> | |
<NotFound> | |
<LayoutView Layout="@typeof(MainLayout)"> | |
<p>Sorry, there's nothing at this address.</p> | |
</LayoutView> | |
</NotFound> | |
</Router> | |
@code { | |
private List<Assembly> assemblies = new List<Assembly>(); | |
private async Task OnNavigateAsync(NavigationContext args) | |
{ | |
try | |
{ | |
if (args.Path.StartsWith("users")) | |
{ | |
var loadedAssemblies = await assemblyLoader.LoadAssembliesAsync( | |
new List<string>() { "BlazorLazyLoading.Client.Users.dll" }); | |
assemblies.AddRange(loadedAssemblies); | |
} | |
} | |
catch (Exception ex) | |
{ | |
} | |
} | |
} |
Na początku w linijce 4 wstrzykuję do komponentu obiekt typu LazyAssemblyLoader, który właśnie będzie odpowiedzialny za pobrania na żądanie dodatkowych assembly. Przed tą linijką dodałem dwa usingi.
Kolejną zmianą jest dodanie elementu Navigating (linijki 8-12) do komponentu Router. Znajduje się w nim kawałek html, który wyświetli się w momencie ładowania widoku/assembly).
W linijce 8 znajduje się podpięcie handlera OnNavigateAsync, który jest uruchamiany w momencie nawigacji między poszczególnymi stronami. W kontekście, który jest przekazywany, otrzymujemy informacje między innymi o tym, na jaką stronę użytkownik chce przejść. Dzięki czemu możemy w przypadku przejścia do strony /users załadować assembly z modułem.
Sama implementacja handlera znajduje się w linijkach 25-40. Korzystamy z właściwości Path przekazanego kontekstu. Sprawdzamy, czy zaczyna się od users i jeśli tak to za pomocą wcześniej wstrzykniętego assemblyLoader wczytujemy assembly BlazorLazyLoading.Client.Users.dll.
Tutaj niestety musimy przekazać nazwę assembly w postaci stringu. Nie możemy skorzystać z konstrukcji typeof(Users.UsersModule).Assembly, która była wcześniej, ponieważ tego assembly jeszcze nie ma w aplikacji, więc zakończy się to błędem.
W realnej aplikacji i tak prawdopodobnie mielibyśmy trochę inaczej zorganizowane budowanie menu aplikacji. Po zalogowaniu użytkownika pobralibyśmy listę jego uprawnień, więc gdzieś w tym miejscu można by dorzucić mapowanie adresów stron na assembly, aby na sztywno tego tutaj nie hardkodować.
Działanie Lazy Loading
Lazy Loading modułu Users już działa. Możemy uruchomić aplikację i zobaczyć, jak się zachowa. Po przejściu do strony /users za pierwszym razem widać przez ułamek sekundy html dodany do elementu Navigating. Pojawia się on w momencie pobrania assembly. Przy kolejnych przejściach widok wyświetla się od razu:
Przykład
Na githubie (https://github.com/danielplawgo/BlazorLazyLoading) znajduje się przykład do tego wpisu. Zachęcam do pobrania i własnej zabawy. Po jego pobraniu nie są potrzebne żadne dodatkowe konfiguracje. Można od razu uruchomić projekt.
Podsumowanie
Lazy Loading assembly jest kolejnym mechanizmem, za pomocą którego możemy rozwiązać problem długiego startu aplikacji. Myślę, że może on przydać się szczególnie w bardziej rozbudowanych aplikacjach, gdzie i tak aplikacje podzielimy na moduły. Jak sam widzisz dodanie samego doczytywania modułów nie jest już skomplikowane i problematyczne.
1 thought on “Blazor Lazy Loading”