Zanim dotarliśmy do tego artykułu, już zagłębiliśmy się w to, czym jest Blazor. Zarówno z technicznego, jak i biznesowego punktu widzenia. Jeśli przegapiłeś którykolwiek z poprzednich artykułów, przejrzyj nasz blog i przeczytaj. Rozszerzmy definicję Blazor jeszcze bardziej. Blazor to tak zwany stanowy framework aplikacji. Co dokładnie oznacza "stan"?
Czym jest stan aplikacji?
Jako użytkownik aplikacji, najprawdopodobniej nigdy nie zastanawiałeś się, jak to się dzieje, że Twój koszyk zawsze tam jest, gdy wracasz na stronę sklepu. W jakiś sposób Twoja strona na Facebooku pamięta, kim jesteś, gdy ją odwiedzasz. Niektóre witryny zapamiętują nawet preferencje językowe i ładują się w trybie ciemnym, jeśli tak wybrałeś podczas poprzedniej wizyty.
Wszystko, co jest generowane dla konkretnego użytkownika w aplikacji, będzie nazywane jego stanem. Będzie to wszystko, od danych użytkownika, ustawień użytkownika, najnowszych danych wejściowych użytkownika, aż po hierarchię instancji komponentów i ich drzew renderowania. To, gdzie zaczyna się i kończy, zależy od potrzeb biznesowych i implementacji dewelopera.
Czy stan ma w ogóle znaczenie?
Aby to sobie wyobrazić, wyobraź sobie aplikację, w której stan nie jest utrzymywany prawidłowo. Wypełniasz formularz podatkowy, długi na 6 stron, obliczenia w tle i w jakiś sposób twoje połączenie internetowe zawodzi i rozłącza cię na sekundę. W ten sposób cały formularz zostaje odświeżony, więc musisz go ponownie wypełnić.
W innym przypadku - użytkownik musi się zalogować, aby zobaczyć chronione lub wrażliwe dane. Ale ponieważ stan nie jest zarządzany we właściwy sposób, za każdym razem, gdy przeładowujesz stronę - musisz zalogować się ponownie.
Nie będziemy używać tej aplikacji przez długi czas, prawda? Właśnie dlatego prawidłowe zarządzanie stanem w aplikacji stanowej ma kluczowe znaczenie dla jej sukcesu biznesowego. W dzisiejszych czasach stan jest krytyczną częścią wszystkich aplikacji, w których zachodzi interakcja z użytkownikiem.
A co ze stanem w Blazor?
Jak już omówiliśmy w artykule: "Jak rozpocząć Blazor Project prawo", istnieją poważne różnice architektoniczne między Blazor Server i Client-side. Znajduje to również odzwierciedlenie w zarządzaniu stanem aplikacji.
W Blazor Server stan jest głównie przechowywany po stronie serwera, w pamięci, w tak zwanym obwodzie. Może to stać się problemem, gdy opóźnienie sieci jest wysokie lub połączenie zostanie utracone po stronie użytkownika. Domyślnie framework Blazor będzie próbował ponownie połączyć się z tym samym obwodem. Jeśli jednak opóźnienie połączenia będzie zbyt duże, zostanie utworzony nowy obwód, a stan użytkownika zostanie utracony. Jednym ze środków zaradczych byłoby zachowanie stanu poza pamięcią serwera. Tak, zajmiemy się tym poniżej.
W Blazor WebAssembly zarządzanie stanem jest nieco inne, ponieważ jest w pełni hostowane w przeglądarce użytkownika. Bez zależności od pamięci serwera, opóźnienia lub problemy z połączeniem sieciowym nie miałyby wpływu na integralność aplikacji. Jednak przeładowanie aplikacji lub ponowne odwiedzenie strony spowodowałoby odświeżenie aplikacji do stanu początkowego. Tak więc persystencja miałaby sens również w tym przypadku. I tak, zajmiemy się również tym zagadnieniem.
Najprostsze podejście do zarządzania stanami - CascadingValue
Co to jest? CascadingValue pozwala na kaskadowe udostępnianie określonego obiektu, od komponentu nadrzędnego do dowolnego poziomu komponentów podrzędnych (jeśli chcesz poznać wszystkie szczegóły - tutajmożesz). A co jeśli tym obiektem jest pamięć stanu? A co, jeśli owiniesz go wokół wszystkich komponentów, którym chcesz udostępnić stan? Idąc o krok dalej, można nawet owinąć CascadingValue wokół całego kontekstu aplikacji. Wartość, którą przenosi, może być następnie dostępna dla wszystkich komponentów w tej aplikacji.
Najpierw stwórzmy klasę, która będzie odpowiedzialna za przechowywanie informacji o stanie. Jest to znacznie czystsze podejście i łatwiejsze w utrzymaniu, ponieważ wszystkie zmienne stanu znajdują się w tej samej klasie.
public class UserApplicationState
{
public string AuthenticationToken { get; set; }
public CartState Cart { get; set; }
//….
}
Mamy obiekt stanu. Przejdźmy do komponentów wywodzących się z LayoutComponentBase. Będą one eksponować RenderFragment o nazwie Body. Tutaj nastąpi zawijanie, z pomocą CascadingValue i naszej instancji UserApplicationState.
<CascadingValue Value="@UserApplicationState">
@Body
</CascadingValue>
@code {
protected UserApplicationState UserApplicationState = new();
}
Dzięki tym kilku linijkom włączyliśmy wszystkie komponenty, które będą częścią kontekstu aplikacji, do korzystania z naszej niestandardowej klasy zarządzania stanem. Aby ją pobrać, zadeklaruj CascadingParamter w komponencie wykorzystującym.
[CascadingParameter] protected UserApplicationState UserApplicationState { get; set; }
To najprostszy sposób udostępniania stanów w aplikacji Blazor. Należy pamiętać, że w przypadku CascadingValue możemy wprowadzić stany dla określonych części naszej aplikacji, niekoniecznie globalnej. Nie ma ograniczeń co do liczby obiektów tego typu, które można zadeklarować.
Bardziej seksowne podejście do zarządzania stanem - wstrzykiwanie zależności
Znacznie bardziej zaawansowanym i rozsądnym architektonicznie podejściem do zarządzania stanem jest wstrzykiwanie zależności. Aby to zadziałało, wprowadzilibyśmy usługę kontenera. Obiekt ten będzie przechowywał nie tylko właściwości stanu, ale także metody do manipulowania nimi. Opierając się na naszym poprzednim przykładzie, oto jak można zaimplementować taki wrapper.
public class UserApplicationState : IUserApplicationState
{
public string AuthenticationToken { get; private set; }
public CartState Cart { get; private set; }
//...
public void StoreAuthenticationToken(string token) => AuthenticationToken = token;
public void StashCart(CartState cart) => Cart = cart;
//...
}
Następnym krokiem będzie dodanie tej usługi do kontenera zależności aplikacji. W tym celu musimy przejść do pliku Program.cs, w którym skonfigurowany jest DI.
var builder = WebApplication.CreateBuilder(args);
//...
builder.Services.AddScoped<IUserApplicationState, UserApplicationState>();
var app = await builder.Build();
Zamiast przekazywać obiekt stanu jako parametr, jak to miało miejsce w przypadku CascadingValue, możemy wstrzyknąć go do komponentów lub nawet włączyć go dla całych obszarów, wstrzykując go do pliku _Imports.razor.
[Inject] protected IUserApplicationState UserApplicationState { get; set; }
Jak sama nazwa wskazuje, nasza usługa jest odpowiedzialna za zarządzanie stanem aplikacji na poziomie użytkownika. W związku z tym najbardziej sensowne jest zarejestrowanie jej jako typu Scoped. Jest to również wskazówka. W przypadku stanu współdzielonego w całej aplikacji moglibyśmy zarejestrować go jako singleton.
Utrzymywanie stanu po stronie serwera, ale poza aplikacją
Wiedza o tym, jak podejść do zarządzania stanem, to tylko połowa sukcesu. W przypadku ponownego załadowania aplikacji WebAssembly lub nowego połączenia Circuit dla Blazor po stronie serwera, obiekt stanu zostanie ponownie zainicjowany. Jak moglibyśmy utrzymać go w obu przypadkach?
W zależności od podejścia do przechowywania informacji o stanie, dostępnych jest kilka ważnych opcji. Jako punkt odniesienia powinniśmy rozważyć utrwalanie jako JSON. Serializacja i deserializacja tych obiektów jest nie tylko uniwersalna, ale także bardzo wydajna. Na razie załóżmy, że chcielibyśmy przechowywać je gdzieś w chmurze. Moglibyśmy wykorzystać dowolny rodzaj przechowywania plików (jako bloby na Azure lub wiadra na Google Cloud Platform) i zapisywać JSON-y ze stanem jako zwykłe pliki. Nie jest to jednak efektywna opcja odczytu i zapisu. Lepszym sposobem byłoby zrobienie tego przy wsparciu jakiejś bazy danych. Jest to nie tylko bezpieczniejsze, ale także zapewnia znacznie lepszą wydajność.
Należy pamiętać, że w pewnym momencie życia aplikacji dane, które są utrwalane, nie są już stanem, ale stają się danymi aplikacji. Traktowanie koszyka jako obiektu stanu ma całkowity sens, dopóki nie zostanie opłacony. Nie rozszerzaj zbytnio tego, czym jest "stan". Bezpieczna wskazówka: jeśli dane nie muszą być nadal dostępne po 24 godzinach bezczynności użytkownika, można je traktować jako stan i utrwalać. W innych przypadkach należy je traktować jako dane aplikacji i przechowywać.
Magazyn przeglądarki - SessionStorage i LocalStorage
Utrwalając stan w chmurze, podejmujemy się kolejnego zadania konserwacyjnego. Nawet jeśli zarządzanie stanem nie jest krytyczne, powinno być monitorowane i utrzymywane w celu zapewnienia płynnego doświadczenia użytkownika. Co więcej, jego zabezpieczenie może być wyzwaniem samym w sobie. Istnieje inny sposób utrzymywania stanu, który eliminuje wszystkie potencjalne trudności.
Każda nowoczesna przeglądarka udostępnia części pamięci do wykorzystania przez aplikacje internetowe. Nie przechowują one dużej ilości danych, ale wystarczają do zarządzania stanem. Co więcej, obsługa pamięci przeglądarki w Blazor jest dostarczana z funkcją szyfrowania po wyjęciu z pudełka. Przetrwałe dane nie są zatem czytelne dla człowieka, a wszelkie próby manipulowania wartościami spowodują pełne ponowne uruchomienie stanu.
Pamięć przeglądarki pozwala na dwa różne okresy użytkowania. Pamięć sesji, jak sama nazwa wskazuje, będzie przechowywana tak długo, jak aktywna jest sesja aplikacji. Z drugiej strony, pamięć lokalna będzie utrzymywana tak długo, jak długo użytkownik nie wyczyści jej ręcznie.
Korzystanie z obu magazynów w rozwiązaniach Blazor jest niezwykle proste. Ponieważ są one domyślnie tworzone przez framework, sprowadza się to do wstrzyknięcia ich usług do komponentów, które ich wymagają.
[Inject] protected ProtectedLocalStorage LocalStorage { get; set; }
[Inject] protected ProtectedSessionStorage SessionStorage { get; set; }
Nadal pracując z naszą klasą UserApplicationState, oto jak ją utrwalić i odczytać z pamięci przeglądarki.
await SessionStorage.SetAsync(
nameof(UserApplicationState), System.Text.Json.JsonSerializer.Serialize(state)
);
var state = await SessionStorage.GetAsync<UserApplicationState>(nameof(UserApplicationState));
I tylko krótkie spojrzenie na samą pamięć masową, aby zaprezentować, jak wygląda przechowywana wartość po zaszyfrowaniu domyślnym algorytmem.
Jeśli zdecydujesz się wykorzystać pamięć masową przeglądarki w swojej aplikacji, zdecydowanie zalecam zaimplementowanie usługi kontenera. Wstrzykiwanie pamięci masowej bezpośrednio do wszystkich komponentów może okazać się koszmarem konserwacji kodu w przyszłości. Jeśli go zawiniesz - ograniczasz zakres możliwych aktualizacji kodu lub awarii kodu do tej jednej klasy usług.
Nowa sekcja: gdzie dowiedzieć się więcej!
Otrzymaliśmy kilka opinii na temat naszej serii! W związku z tym postanowiliśmy dodać nową sekcję do niektórych artykułów pod tytułem: gdzie dowiedzieć się więcej! Będzie to sekcja z linkami do filmów na YouTube lub innych źródeł w Internecie, w których można zgłębić tematy z artykułu.
Zaczynamy:
- sekcja w oficjalnej dokumentacji Blazor: tutaj
- Poradnik Chrisa Sainty'ego: tutaj
- sesja na kanale dotNET: tutaj
- odcinek Blazor Train o zarządzaniu państwem: tutaj
Co dalej?
Teraz, gdy omówiliśmy podstawy zarządzania stanami i sposoby ich utrzymywania, możemy to rozwinąć! Rzućmy kilka modnych słów, aby zainteresować Ciebie i wyszukiwarkę Google - jednostronna komunikacja, odsprzęganie komponentów, wzorzec redux i globalna obsługa zdarzeń/stanów. Do zobaczenia na następnym?