Udostępnij za pośrednictwem


Zarządzanie stanem w ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję artykułu .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

W tym artykule opisano typowe podejścia do obsługi danych (stanu) użytkownika podczas korzystania z aplikacji i sesji przeglądarki.

Uwaga

Przykłady kodu w tym artykule przyjmują typy odwołań dopuszczających wartość null (NRTs) i statyczną analizę stanu null kompilatora platformy .NET, które są obsługiwane w programie ASP.NET Core na platformie .NET 6 lub nowszym. W przypadku celowania w ASP.NET Core 5.0 lub starszą wersję, usuń oznaczenie typu null (?) z typów w przykładach w artykule.

Utrzymanie stanu użytkownika

Struktura aplikacji po stronie Blazor serwera jest stanowa. W większości przypadków aplikacja utrzymuje połączenie z serwerem. Stan użytkownika jest przechowywany w pamięci serwera w układzie .

Przykłady stanu użytkownika przechowywanego w obwodzie obejmują:

  • Hierarchia wystąpień składników i ich najnowszych danych wyjściowych renderowania w renderowanych interfejsach użytkownika.
  • Wartości pól i właściwości w wystąpieniach składników.
  • Dane przechowywane w wystąpieniach usługi wstrzykiwania zależności (DI), które są ograniczone do obwodu.

Stan użytkownika można również znaleźć w zmiennych JavaScript w pamięci przeglądarki ustawionej za pośrednictwem wywołań międzyoperacyjnych języka JavaScript.

Jeśli użytkownik doświadcza tymczasowej utraty połączenia sieciowego, Blazor próbuje ponownie połączyć użytkownika z oryginalnym obwodem ze stanem oryginalnym. Jednak ponowne połączenie użytkownika z oryginalnym obwodem w pamięci serwera nie zawsze jest możliwe:

  • Serwer nie może zachować odłączonego obwodu na zawsze. Serwer musi zwolnić odłączony obwód po przekroczeniu limitu czasu lub gdy serwer jest pod ciśnieniem pamięci.
  • W środowiskach wdrażania z wieloma serwerami z równoważeniem obciążenia poszczególne serwery mogą zakończyć się niepowodzeniem lub zostać automatycznie usunięte, gdy nie będą już wymagane do obsługi ogólnej liczby żądań. Oryginalny serwer przetwarzający żądania użytkownika może stać się niedostępny, gdy użytkownik spróbuje ponownie połączyć się.
  • Użytkownik może zamknąć i ponownie otworzyć przeglądarkę lub ponownie załadować stronę, co spowoduje usunięcie dowolnego stanu przechowywanego w pamięci przeglądarki. Na przykład wartości zmiennych języka JavaScript ustawiane za pomocą wywołań międzyoperacyjnych języka JavaScript są tracone.

Gdy użytkownik nie może być ponownie połączony z oryginalnym obwodem, użytkownik otrzymuje nowy obwód z pustym stanem. Jest to odpowiednik zamykania i ponownego otwierania aplikacji klasycznej.

Utrzymywać stan między obwodami

Ogólnie rzecz biorąc, należy zachować stan między obwodami, w których użytkownicy aktywnie tworzą dane, a nie tylko je odczytują, gdy już istnieją.

Aby zachować stan między obwodami, aplikacja musi utrwalać dane w innym miejscu przechowywania niż pamięć serwera. Trwałość stanu nie jest automatyczna. Należy wykonać kroki podczas tworzenia aplikacji w celu zaimplementowania stanowej trwałości danych.

Trwałość danych jest zwykle wymagana tylko w przypadku stanu o wysokiej wartości, który użytkownicy wydali nakład pracy na utworzenie. W poniższych przykładach utrwalony stan pozwala zaoszczędzić czas lub wspomaga działania komercyjne.

  • Formularze wieloetapowe w internecie: Czasochłonne jest dla użytkownika ponowne wprowadzanie danych do kilku ukończonych już kroków wieloetapowego formularza, jeśli ich stan zostanie utracony. Użytkownik utraci stan w tym scenariuszu, jeśli opuści formularz i wróci później.
  • Koszyki na zakupy: każdy składnik aplikacji, który reprezentuje potencjalny przychód, może być utrzymywany. Użytkownik, który traci swój stan, a tym samym koszyk zakupów, może zakupić mniej produktów lub usług po powrocie do witryny później.

Aplikacja może utrwalać tylko stan aplikacji. Interfejsy użytkownika nie mogą być zapisywane na stałe, jak na przykład wystąpienia składników i ich drzewa renderowania. Składniki i drzewa renderowania nie są zwykle serializowalne. Aby utrwalyć stan interfejsu użytkownika, taki jak rozwinięte węzły kontrolki widoku drzewa, aplikacja musi używać niestandardowego kodu do modelowania zachowania stanu interfejsu użytkownika jako stanu aplikacji z możliwością serializacji.

Gdzie zachować stan

Typowe lokalizacje istnieją do przechowywania stanu.

Magazyn po stronie serwera

W przypadku trwałej trwałości danych obejmującej wielu użytkowników i urządzeń aplikacja może używać magazynu po stronie serwera. Dostępne opcje:

  • magazyn typu Blob
  • Przechowywanie klucz-wartość
  • Relacyjna baza danych
  • Magazynowanie tabel

Po zapisaniu danych stan użytkownika jest zachowywany i dostępny w każdym nowym obwodzie.

Aby uzyskać więcej informacji na temat opcji magazynu danych platformy Azure, zobacz następujące tematy:

URL

W przypadku przejściowych danych reprezentujących stan nawigacji modeluje dane jako część adresu URL. Przykłady stanu użytkownika modelowane w adresie URL to:

  • Identyfikator wyświetlanej jednostki.
  • Bieżący numer strony w siatce stronicowanej.

Zawartość paska adresu przeglądarki jest zachowywana:

  • Jeśli użytkownik ręcznie ponownie załaduje stronę.
  • Jeśli serwer internetowy stanie się niedostępny, a użytkownik musi ponownie załadować stronę w celu nawiązania połączenia z innym serwerem.

Aby uzyskać informacje na temat definiowania wzorców adresów URL za pomocą @page dyrektywy, zobacz Routing i nawigację w ASP.NET CoreBlazor.

Pamięć przeglądarki

W przypadku danych przejściowych, które użytkownik aktywnie tworzy, często używaną lokalizacją magazynu są kolekcje localStorage i sessionStorage przeglądarki.

  • localStorage jest powiązana z instancją przeglądarki. Jeśli użytkownik ponownie załaduje stronę lub zamknie i ponownie otworzy przeglądarkę, stan będzie się powtarzać. Jeśli użytkownik otworzy wiele kart przeglądarki, stan będzie współdzielony we wszystkich kartach. Dane są utrwalane localStorage do momentu jawnego wyczyszczenia. Dane localStorage dokumentu załadowane w sesji "przeglądania prywatnego" lub "trybie incognito" są czyszczone po zamknięciu ostatniej karty "prywatnej".
  • sessionStorage jest ograniczona do karty przeglądarki. Jeśli użytkownik ponownie załaduje kartę, stan będzie się powtarzać. Jeśli użytkownik zamknie kartę lub przeglądarkę, stan zostanie utracony. Jeśli użytkownik otworzy wiele kart przeglądarki, każda karta ma własną niezależną wersję danych.

Ogólnie rzecz biorąc, sessionStorage jest bezpieczniejszy do użycia. sessionStorage pozwala uniknąć ryzyka, że użytkownik otwiera wiele kart i napotyka następujące elementy:

  • Usterki w przechowywaniu stanu w zakładkach.
  • Mylące zachowanie, gdy karta zastępuje stan innych kart.

localStorage jest lepszym wyborem, jeśli aplikacja musi zachować stan podczas zamykania i ponownego otwierania przeglądarki.

Zastrzeżenia dotyczące korzystania z pamięci przeglądarki:

  • Podobnie jak w przypadku korzystania z bazy danych po stronie serwera, ładowanie i zapisywanie danych jest asynchroniczne.
  • Żądana strona nie istnieje w przeglądarce podczas prerenderingu, więc magazyn lokalny nie jest dostępny podczas prerenderingu.
  • Przechowywanie kilku kilobajtów danych jest uzasadnione dla aplikacji po stronie serwera Blazor. Poza kilkoma kilobajtami należy wziąć pod uwagę implikacje dotyczące wydajności, ponieważ dane są ładowane i zapisywane w sieci.
  • Użytkownicy mogą wyświetlać lub modyfikować dane. ASP.NET Core Data Protection może ograniczyć ryzyko. Na przykład ASP.NET Core Protected Browser Storage używa ASP.NET Core Data Protection.

Pakiety NuGet innych firm udostępniają interfejsy API do pracy z localStorage i sessionStorage. Warto rozważyć wybranie pakietu, który w sposób niewidoczny używa ASP.NET Core Data Protection. Usługa Data Protection szyfruje przechowywane dane i zmniejsza potencjalne ryzyko naruszenia przechowywanych danych. Jeśli dane serializowane w formacie JSON są przechowywane w postaci zwykłego tekstu, użytkownicy mogą wyświetlać dane przy użyciu narzędzi deweloperskich przeglądarki, a także modyfikować przechowywane dane. Zabezpieczanie danych trywialnych nie jest problemem. Na przykład odczytywanie lub modyfikowanie przechowywanego koloru elementu interfejsu użytkownika nie jest istotnym zagrożeniem bezpieczeństwa dla użytkownika lub organizacji. Unikaj zezwalania użytkownikom na inspekcję lub manipulowanie poufnymi danymi.

Ochrona pamięci przeglądarki w ASP.NET Core

ASP.NET Core Protected Browser Storage korzysta z usługi ASP.NET Core Data Protection dla localStorage i sessionStorage.

Uwaga

Usługa Protected Browser Storage korzysta z ASP.NET Core Data Protection i jest obsługiwana tylko w przypadku aplikacji po stronie Blazor serwera.

Ostrzeżenie

Microsoft.AspNetCore.ProtectedBrowserStorage to nieobsługiwany, eksperymentalny pakiet, który nie jest przeznaczony do użytku produkcyjnego.

Pakiet jest dostępny tylko do użycia w aplikacjach platformy ASP.NET Core 3.1.

Konfigurowanie

  1. Dodaj odwołanie do pakietu Microsoft.AspNetCore.ProtectedBrowserStorage.

    Uwaga

    Aby uzyskać instrukcje dodawania pakietów do aplikacji .NET, zobacz artykuły w sekcji Instalowanie pakietów i zarządzanie nimi w temacie Przepływ pracy użycia pakietów (dokumentacja programu NuGet). Sprawdź prawidłowe wersje pakietów pod adresem NuGet.org.

  2. _Host.cshtml W pliku dodaj następujący skrypt wewnątrz tagu zamykającego</body>:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. W Startup.ConfigureServices pliku wywołaj AddProtectedBrowserStorage, aby dodać localStorage i sessionStorage do kolekcji usług:

    services.AddProtectedBrowserStorage();
    

Zapisywanie i ładowanie danych w składniku

W każdym składniku, który wymaga ładowania lub zapisywania danych w magazynie przeglądarki, użyj dyrektywy @inject, aby wstrzyknąć wystąpienie jednego z następujących:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

Wybór zależy od lokalizacji przechowywania przeglądarki, której chcesz użyć. W poniższym przykładzie sessionStorage jest używany:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

Dyrektywę @using można umieścić w pliku aplikacji _Imports.razor zamiast w składniku. _Imports.razor Użycie pliku sprawia, że przestrzeń nazw jest dostępna dla większych segmentów aplikacji lub całej aplikacji.

Aby zachować wartość currentCount w składniku Counter aplikacji na podstawie szablonu projektu Blazor, zmodyfikuj metodę IncrementCount, aby użyć ProtectedSessionStore.SetAsync.

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

W większych, bardziej realistycznych aplikacjach przechowywanie poszczególnych pól jest mało prawdopodobne. Aplikacje częściej przechowują całe obiekty modelu, które obejmują złożony stan. ProtectedSessionStore automatycznie serializuje i deserializuje dane JSON w celu przechowywania złożonych obiektów stanu.

W poprzednim przykładzie kodu, dane currentCount są przechowywane jako sessionStorage['count'] w przeglądarce użytkownika. Dane nie są przechowywane w postaci zwykłego tekstu, ale są chronione przy użyciu ASP.NET Core Data Protection. Zaszyfrowane dane można sprawdzić, jeśli sessionStorage['count'] jest oceniany w konsoli programisty przeglądarki.

Aby odzyskać dane currentCount, gdy użytkownik powróci do składnika Counter, nawet jeśli znajduje się w nowym okręgu, użyj ProtectedSessionStore.GetAsync.

protected override async Task OnInitializedAsync()
{
    var result = await ProtectedSessionStore.GetAsync<int>("count");
    currentCount = result.Success ? result.Value : 0;
}
protected override async Task OnInitializedAsync()
{
    currentCount = await ProtectedSessionStore.GetAsync<int>("count");
}

Jeśli parametry składnika obejmują stan nawigacji, wywołaj ProtectedSessionStore.GetAsync i przypisz wynik w OnParametersSetAsync inny niż null, a nie w OnInitializedAsync. OnInitializedAsync jest wywoływany tylko raz, gdy składnik jest najpierw instancjonowany. OnInitializedAsync nie zostanie ponownie wywołana później, jeśli użytkownik przejdzie do innego adresu URL, pozostając na tej samej stronie. Aby uzyskać więcej informacji, zobacz Cykl życia składników platformy ASP.NET Core Razor.

Ostrzeżenie

Przykłady w tej sekcji działają tylko wtedy, gdy serwer nie ma włączonego prerenderingu. Po włączeniu prerenderingu jest generowany błąd wyjaśniający, że nie można wykonać wywołań międzyoperacyjnych JavaScript, ponieważ składnik jest wstępnie renderowany.

Wyłącz prerendering lub dodaj dodatkowy kod, aby pracować z prerenderingiem. Aby dowiedzieć się więcej na temat pisania kodu, który działa z prerenderingiem, zobacz sekcję Obsługa prerenderingu .

Obsługa stanu ładowania

Ponieważ dostęp do magazynu przeglądarki jest uzyskiwany asynchronicznie za pośrednictwem połączenia sieciowego, zawsze istnieje okres czasu przed załadowaniem i udostępnieniem danych składnikowi. Aby uzyskać najlepsze wyniki, zamiast wyświetlania pustych lub domyślnych danych, renderuj komunikat podczas trwania ładowania.

Jednym z podejść jest śledzenie, czy dane są null, co oznacza, że dane są nadal ładowane. W składniku domyślnym Counter liczba jest przechowywana w elemencie int. Uczyń currentCount typu nullable, dodając znak zapytania (?) do typu (int):

private int? currentCount;

Zamiast bezwarunkowo wyświetlać liczbę i Increment przycisk, wyświetl te elementy tylko wtedy, gdy dane są ładowane, sprawdzając polecenie HasValue:

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

Obsługa prerenderingu

Podczas prerenderingu:

  • Interakcyjne połączenie z przeglądarką użytkownika nie istnieje.
  • Przeglądarka nie ma jeszcze strony, na której można uruchomić kod JavaScript.

localStorage lub sessionStorage nie są dostępne podczas prerenderingu. Jeśli składnik próbuje wchodzić w interakcję z pamięcią, zostanie wygenerowany błąd wyjaśniający, że nie można wydać wywołań interop w JavaScript, ponieważ składnik jest prerenderowany.

Jednym ze sposobów usunięcia błędu jest wyłączenie prerenderingu. Zazwyczaj jest to najlepszy wybór, jeśli aplikacja intensywnie korzysta z magazynu przechowywania danych opartego na przeglądarce. Wstępne renderowanie zwiększa złożoność i nie przynosi korzyści aplikacji, ponieważ nie może ona wstępnie wyrenderować żadnej użytecznej zawartości, dopóki localStorage lub sessionStorage nie będą dostępne.

Aby wyłączyć prerendering, wskaż tryb renderowania z parametrem prerender ustawionym na false w składniku na najwyższym poziomie hierarchii składników aplikacji, który nie jest składnikiem głównym.

Uwaga

Tworzenie interaktywnego składnika głównego, takiego jak składnik App, nie jest obsługiwane. Dlatego prerendering nie może być wyłączony bezpośrednio przez składnik App.

W przypadku aplikacji opartych na szablonie projektu Blazor Web App, prerendering jest zwykle wyłączony, gdy składnik Routes jest używany w składniku App (Components/App.razor).

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Ponadto wyłącz prerendering dla HeadOutlet komponentu:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Aby uzyskać więcej informacji, zobacz tryby renderowania ASP.NET Core.

Aby wyłączyć prerendering, otwórz plik _Host.cshtml i zmień atrybut render-mode pomocnika tagów składnika na Server:

<component type="typeof(App)" render-mode="Server" />

Gdy prerenderowanie jest wyłączone, prerenderowanie zawartości jest wyłączone.

Wstępne przetwarzanie może być przydatne w przypadku innych stron, które nie używają localStorage ani sessionStorage. Aby zachować wstępne ładowanie, odrocz operację ładowania, dopóki przeglądarka nie zostanie połączona z obwodem. Poniżej przedstawiono przykład przechowywania wartości licznika:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount;
    private bool isConnected;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedLocalStore.GetAsync<int>("count");
        currentCount = result.Success ? result.Value : 0;
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

Uwzględnianie zachowania stanu dla wspólnego dostawcy

Jeśli wiele składników korzysta z magazynu opartego na przeglądarce, implementowanie kodu dostawcy stanu wiele razy powoduje duplikowanie kodu. Jedną z opcji unikania duplikowania kodu jest utworzenie składnika nadrzędnego dostawcy stanu, który hermetyzuje logikę dostawcy stanu. Składniki podrzędne mogą pracować z utrwalanymi danymi bez względu na mechanizm trwałości stanu.

W poniższym przykładzie składnika CounterStateProvider dane licznika są zapisywane do sessionStorage, a faza ładowania jest obsługiwana poprzez nierealizowanie renderowania zawartości podrzędnej do zakończenia ładowania stanu.

Składnik CounterStateProvider zajmuje się prerenderowaniem, nie ładując stanu aż do momentu po renderowaniu składnika w metodzie cyklu życia OnAfterRenderAsync, która nie jest wykonywana podczas prerenderowania.

Podejście opisane w tej sekcji nie jest w stanie wyzwolić ponownego renderowania wielu związanych komponentów na tej samej stronie. Jeśli jeden z subskrybowanych składników zmienia stan, ponownie się renderuje i może wyświetlać zaktualizowany stan, natomiast inny składnik na tej samej stronie, wyświetlający ten stan, pokazuje nieaktualne dane do czasu swojego kolejnego ponownego renderowania. W związku z tym podejście opisane w tej sekcji najlepiej nadaje się do używania stanu w jednym składniku na stronie.

CounterStateProvider.razor:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task IncrementCount()
    {
        CurrentCount++;
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task IncrementCount()
    {
        CurrentCount++;
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

Uwaga

Aby uzyskać więcej informacji na temat RenderFragment, zobacz ASP.NET Core Razor components.

Aby stan był dostępny dla wszystkich składników w aplikacji, owijaj składnik CounterStateProvider wokół składnika Router (<Router>...</Router>) w składniku Routes z globalnym interaktywnym renderowaniem po stronie serwera (interaktywne SSR).

W składniku App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

W składniku Routes (Components/Routes.razor):

Aby użyć CounterStateProvider składnika, opakuj wystąpienie składnika wokół dowolnego innego składnika, który wymaga dostępu do stanu licznika. Aby udostępnić stan wszystkim składnikom w aplikacji, owiń składnik Router wokół składnika CounterStateProvider w składniku App (App.razor):

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

Uwaga

Od wydania wersji ASP.NET Core 5.0.1 i w przypadku wszelkich dodatkowych wydań 5.x składnik Router zawiera parametr PreferExactMatches ustawiony na wartość @true. Aby uzyskać więcej informacji, zobacz Migracja z platformy ASP.NET Core w wersji 3.1 do wersji 5.0.

Zawinięte składniki odbierają i mają możliwość modyfikacji stanu utrwalonego licznika. Następujący Counter składnik implementuje wzorzec:

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            await CounterStateProvider.IncrementCount();
        }
    }
}

Poprzedni składnik nie jest wymagany do interakcji z ProtectedBrowserStorage, ani nie uczestniczy w fazie "ładowania".

Ogólnie rzecz biorąc, zalecany jest wzorzec składnika nadrzędnego dostawcy stanu:

  • Aby korzystać ze stanu w wielu komponentach.
  • Jeśli istnieje tylko jeden obiekt stanu najwyższego poziomu do przechowywania.

Aby zachować wiele różnych obiektów stanu i wykorzystywać różne podzbiory obiektów w różnych miejscach, lepiej unikać globalnego zachowywania stanu.

Stan użytkownika utworzony w Blazor WebAssembly aplikacji jest przechowywany w pamięci przeglądarki.

Przykłady stanu użytkownika przechowywanego w pamięci przeglądarki:

Gdy użytkownik zamknie i ponownie otworzy przeglądarkę lub ponownie załaduje stronę, stan użytkownika przechowywany w pamięci przeglądarki zostanie utracony.

Uwaga

Usługa Protected Browser Storage (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage przestrzeń nazw) opiera się na ASP.NET Core Data Protection i jest obsługiwana tylko w przypadku aplikacji po stronie Blazor serwera.

Utrwalanie stanu między sesjami przeglądarki

Ogólnie rzecz biorąc, należy zachować stan między sesjami przeglądarki, w których użytkownicy aktywnie tworzą dane, a nie tylko odczytują dane, które już istnieją.

Aby zachować stan między sesjami przeglądarki, aplikacja musi utrwalać dane w innej lokalizacji przechowywania niż pamięć przeglądarki. Trwałość stanu nie jest automatyczna. Należy wykonać kroki podczas tworzenia aplikacji w celu zaimplementowania stanowej trwałości danych.

Trwałość danych jest zwykle wymagana tylko w przypadku stanu o wysokiej wartości, który użytkownicy wydali nakład pracy na utworzenie. W poniższych przykładach przechowywanie stanu pozwala zaoszczędzić czas lub wspomaga działania komercyjne.

  • Formularze wieloetapowe internetowe: czasochłonne jest dla użytkownika ponowne wprowadzanie danych dla kilku ukończonych kroków wieloetapowego formularza internetowego, jeśli ich stan zostanie utracony. Użytkownik utraci stan w tym scenariuszu, jeśli opuści formularz i powróci do niego później.
  • Koszyki na zakupy: każdy składnik aplikacji, który reprezentuje potencjalny przychód, może być utrzymywany. Użytkownik, który traci swój stan, a tym samym koszyk zakupów, może zakupić mniej produktów lub usług po powrocie do witryny później.

Aplikacja może utrwalać tylko stan aplikacji. Interfejsy użytkownika nie mogą być zapisywane, takie jak instancje komponentów i ich drzewa renderowania. Składniki i drzewa renderowania nie są zwykle serializowalne. Aby utrwalyć stan interfejsu użytkownika, taki jak rozwinięte węzły kontrolki widoku drzewa, aplikacja musi używać niestandardowego kodu do modelowania zachowania stanu interfejsu użytkownika jako stanu aplikacji z możliwością serializacji.

Gdzie przechowywać stan

Istnieją typowe lokalizacje dla przechowywania stanu.

Magazyn po stronie serwera

W przypadku trwałej trwałości danych obejmującej wielu użytkowników i urządzeń aplikacja może używać niezależnego magazynu po stronie serwera dostępnego za pośrednictwem internetowego interfejsu API. Dostępne opcje:

  • Magazyn obiektów blob
  • Magazyn klucz-wartość
  • Relacyjna baza danych
  • Przechowywanie tabel

Po zapisaniu danych stan użytkownika jest zachowywany i dostępny w każdej nowej sesji przeglądarki.

Ponieważ Blazor WebAssembly aplikacje działają w całości w przeglądarce użytkownika, wymagają dodatkowych środków w celu uzyskania dostępu do bezpiecznych systemów zewnętrznych, takich jak usługi przechowywania i bazy danych. Aplikacje Blazor WebAssembly są zabezpieczane w taki sam sposób jak aplikacje jednostronicowe. Zazwyczaj aplikacja uwierzytelnia użytkownika za pośrednictwem OAuth/OpenID Connect (OIDC), a następnie wchodzi w interakcje z usługami magazynu i bazami danych za pośrednictwem wywołań API do aplikacji po stronie serwera. Aplikacja po stronie serwera pośredniczy w transferze danych między aplikacją Blazor WebAssembly a usługą magazynu lub bazą danych. Aplikacja Blazor WebAssembly utrzymuje tymczasowe połączenie z aplikacją po stronie serwera, podczas gdy aplikacja po stronie serwera ma trwałe połączenie z pamięcią.

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Aby uzyskać więcej informacji na temat opcji magazynu danych platformy Azure, zobacz następujące tematy:

URL

W przypadku przejściowych danych reprezentujących stan nawigacji modeluje dane jako część adresu URL. Przykłady stanu użytkownika modelowane w adresie URL to:

  • Identyfikator wyświetlanej jednostki.
  • Bieżący numer strony w siatce stronicowanej.

Zawartość paska adresu przeglądarki jest zachowywana, jeśli użytkownik ręcznie ponownie załaduje stronę.

Aby uzyskać informacje na temat definiowania wzorców adresów URL za pomocą @page dyrektywy, zobacz Routing i nawigację ASP.NET CoreBlazor.

Przechowywanie przeglądarki

W przypadku danych przejściowych, które użytkownik aktywnie tworzy, często używanym miejscem przechowywania są kolekcje localStorage przeglądarki sessionStorage.

  • localStorage jest powiązana z instancją przeglądarki. Jeśli użytkownik ponownie załaduje stronę lub zamknie i ponownie otworzy przeglądarkę, stan będzie się powtarzać. Jeśli użytkownik otworzy wiele kart przeglądarki, stan zostanie wspólny dla wszystkich kart. Dane przechowywane localStorage utrzymują się do momentu jawnego wyczyszczenia. Dane localStorage dokumentu załadowane w sesji "przeglądania prywatnego" lub "trybie incognito" są czyszczone po zamknięciu ostatniej karty "prywatnej".
  • sessionStorage jest ograniczona do karty przeglądarki. Jeśli użytkownik ponownie załaduje kartę, stan będzie się powtarzać. Jeśli użytkownik zamknie kartę lub przeglądarkę, stan zostanie utracony. Jeśli użytkownik otworzy wiele kart przeglądarki, każda karta ma własną niezależną wersję danych.

Uwaga

localStorage i sessionStorage mogą być używane w Blazor WebAssembly aplikacjach, ale tylko przez pisanie własnego kodu lub z użyciem pakietu innej firmy.

Ogólnie rzecz biorąc, sessionStorage jest bezpieczniejszy do użycia. sessionStorage pozwala uniknąć ryzyka, że użytkownik otwiera wiele kart i napotyka następujące problemy:

  • Usterki w przechowywaniu stanu między kartami.
  • Dezorientujące zachowanie, gdy jedna karta nadpisuje stan innych kart.

localStorage jest lepszym wyborem, jeśli aplikacja musi zachować stan podczas zamykania i ponownego otwierania przeglądarki.

Ostrzeżenie

Użytkownicy mogą wyświetlać lub ingerować w dane przechowywane w localStorage i sessionStorage.

Usługa kontenera stanu w pamięci operacyjnej

Zagnieżdżone składniki zazwyczaj wiążą dane przy użyciu chained bind, jak opisano w powiązaniu danych ASP.NET CoreBlazor. Zagnieżdżone i niezagnieżdżone składniki mogą udostępniać dostęp do danych przy użyciu zarejestrowanego kontenera stanu w pamięci. Niestandardowa klasa kontenera stanu może używać przypisywalnego obiektu Action do powiadamiania składników w różnych częściach aplikacji o zmianach stanu. W poniższym przykładzie:

  • Para składników używa kontenera stanu do śledzenia właściwości.
  • Jeden element w poniższym przykładzie jest zagnieżdżony w innym elemencie, ale zagnieżdżanie nie jest wymagane, aby to podejście działało.

Ważne

W przykładzie w tej sekcji pokazano, jak utworzyć usługę kontenera stanu w pamięci, zarejestrować usługę i użyć jej w składnikach. W tym przykładzie dane nie są utrwalane bez dalszego programowania. W przypadku trwałego przechowywania danych kontener stanu musi przyjąć podstawowy mechanizm magazynu, który przetrwa, kiedy pamięć przeglądarki zostanie wyczyszczona. Można to osiągnąć za pomocą localStorage/sessionStorage lub innej technologii.

StateContainer.cs:

public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Aplikacje po stronie klienta (Program plik):

builder.Services.AddSingleton<StateContainer>();

Aplikacje po stronie serwera (Program plik, ASP.NET Core na platformie .NET 6 lub nowszym):

builder.Services.AddScoped<StateContainer>();

Aplikacje po stronie serwera (Startup.ConfigureServices z Startup.cs ASP.NET Core wcześniejsze niż 6.0):

services.AddScoped<StateContainer>();

Shared/Nested.razor:

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the Nested component
    </button>
</p>

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = 
            $"New value set in the Nested component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

StateContainerExample.razor:

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the State Container Example component
    </button>
</p>

<Nested />

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = "New value set in the State " +
            $"Container Example component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

Powyższe składniki implementują IDisposable, a delegaty OnChange są odsubskrybowywane w metodach Dispose, które są wywoływane przez framework podczas usuwania składników. Aby uzyskać więcej informacji, zobacz usuwanie komponentów ASP.NET Core Razor.

Kaskadowe wartości i parametry

Użyj kaskadowych wartości i parametrów do zarządzania stanem, przekazując dane ze składnika Razor przodka do składników potomnych.

Wartości kaskadowe na poziomie głównym z CascadingValueSource<TValue> zezwalają na Razor powiadomienia subskrybenta składnika o zmienionych wartościach kaskadowych. Aby uzyskać więcej informacji i przykład roboczy, zobacz przykład NotifyingDalek w ASP.NET Core Blazor kaskadowych wartości i parametrów.

Dodatkowe podejścia

Podczas implementowania niestandardowego magazynu stanu przydatne jest zastosowanie kaskadowych wartości i parametrów:

  • Aby korzystać ze stanu w wielu komponentach.
  • Jeśli istnieje tylko jeden obiekt stanu najwyższego poziomu do przechowywania.

Rozwiązywanie problemów

W przypadku korzystania z niestandardowej usługi zarządzania stanem, w której chcesz obsługiwać modyfikacje stanu spoza kontekstu synchronizacji Blazor (na przykład z czasomierza lub usługi w tle), wszystkie komponenty wykorzystujące muszą umieścić wywołanie StateHasChanged w ComponentBase.InvokeAsync. Dzięki temu powiadomienie o zmianie jest obsługiwane w kontekście synchronizacji programu renderatora.

Gdy usługa zarządzania stanem nie wywołuje StateHasChanged w kontekście synchronizacji Blazor, zgłaszany jest następujący błąd:

System.InvalidOperationException: "Bieżący wątek nie jest skojarzony z dyspozytorem. Użyj metody InvokeAsync(), aby przełączyć proces wykonawczy na dyspozytor podczas wywoływania renderowania lub aktualizacji stanu komponentu.

Aby uzyskać więcej informacji i przykład sposobu rozwiązywania tego błędu, zobacz ASP.NET Core Razor rendering składnika.

Dodatkowe zasoby