Управление состоянием ASP.NET Core Blazor

Примечание.

Это не последняя версия этой статьи. Сведения о текущем выпуске см. в ASP.NET версии Core 8.0 этой статьи.

В этой статье описываются распространенные методики сохранения данных пользователя (состояния) во время использования приложения и во время выполнения сеансов браузера.

Примечание.

Примеры кода в этой статье используют типы ссылок, допускающие значение NULL (NRTs) и статический анализ состояния .NET компилятора NULL, которые поддерживаются в ASP.NET Core в .NET 6 или более поздней версии. При назначении ASP.NET Core 5.0 или более ранней версии удалите обозначение типа NULL (?) из типов в примерах статьи.

Обслуживание состояния пользователя

Серверная часть Blazor — это платформа приложений с отслеживанием состояния. В большинстве случаев приложение поддерживает подключение к серверу. Состояние пользователя хранится в памяти сервера в канале.

Ниже приведены примеры данных о состоянии пользователя, хранящиеся в канале.

  • Иерархия экземпляров компонента и их последних выходных данных отрисовки в преобразованном для просмотра пользовательском интерфейсе.
  • Значения полей и свойств в экземплярах компонента.
  • Данные, хранящиеся во внедрениях зависимостей (DI) экземпляров службы, областью действия которых является канал.

Данные о состоянии пользователя также можно найти в переменных JavaScript в памяти браузера, заданной с помощью вызовов взаимодействия с JavaScript.

Если пользователь испытывает временный сбой сетевого подключения, Blazor пытается повторно подключить пользователя к исходному каналу с сохранением исходного состояния. Однако повторное подключение пользователя к исходному каналу в памяти сервера не всегда возможно.

  • Сервер не может постоянно хранить отключенную цепь. Сервер должен освободить отключенную цепь после истечения времени ожидания или при нехватке памяти на сервере.
  • В средах развертывания с несколькими серверами и балансировкой нагрузки отдельные серверы могут выйти из строя или быть автоматически удалены, если они больше не требуются для обработки общего объема запросов. В этом случае исходные запросы на обработку на сервере могут стать недоступными для пользователя, когда он попытается подключиться повторно.
  • Пользователь может закрыть и повторно открыть браузер или перезагрузить страницу, которая удаляет любое состояние, удерживающееся в памяти браузера. Например, теряются значения переменных JavaScript, заданные через вызовы взаимодействия с JavaScript.

Если пользователь не может повторно подключиться к исходному каналу, он получает новый канал с пустым состоянием. Это эквивалентно закрытию и повторному открытию классического приложения.

Сохранение состояния при смене каналов

Как правило, состояния поддерживается при смене каналов, где пользователи активно могут создавать данные, а не просто считывать уже существующие данные.

Чтобы сохранять состояние при смене каналов, приложение должно хранить данные не в памяти сервера, а в другом месте. Сохраняемость состояния не обеспечивается автоматически. При разработке приложения необходимо выполнить определенные действия для реализации сохраняемости данных с отслеживанием состояния.

Сохраняемость данных, как правило, требуется только для состояний высокой ценности, на создание которых пользователь затратил значительные усилия. В следующих примерах сохранение состояния экономит время или средства в коммерческих действиях.

  • Многофакторные веб-формы: требуется время для повторного ввода данных для нескольких завершенных шагов многофакторной веб-формы, если их состояние потеряно. В этом сценарии пользователь потеряет данные о своем состоянии, если он выйдет из формы и вернется в нее позже.
  • Корзины покупок: любой коммерчески важный компонент приложения, представляющего потенциальный доход, может быть сохранен. Пользователь, который теряет свое состояние, и, следовательно, свою корзину с покупками, может приобрести меньше товаров или услуг, когда вернется на сайт позже.

Приложение может сохранять только состояния приложения. Пользовательские интерфейсы не могут быть сохранены, например экземпляры компонентов и их деревья отрисовки. Компоненты и деревья отрисовки обычно не являются сериализуемыми. Чтобы сохранить состояние пользовательского интерфейса, например развернутые узлы элементов управления иерархического представления, в приложении должен быть пользовательский код для моделирования поведения этого интерфейса как сериализуемого состояния приложения.

Место сохранения состояния

Данные о состоянии могут храниться в общих расположениях:

Хранилище на стороне сервера

Для постоянного хранения данных для нескольких пользователей и устройств приложение может использовать хранилище на стороне сервера. Возможные варианты:

  • Хранилище BLOB-объектов
  • Хранилище значений ключей
  • Реляционная база данных
  • Хранилище таблиц

После сохранения данных состояние пользователя сохраняется и становится доступным во всех новых каналах.

Дополнительные сведения о вариантах хранения данных в Azure см. здесь:

URL

Для временных данных, представляющих состояние навигации, моделируют данные как часть URL-адреса. Ниже приведены примеры данных о состоянии пользователя, которые моделируются в URL-адресе.

  • Идентификатор просматриваемой сущности.
  • Номер текущей страницы в постраничной сетке.

Содержимое адресной строки браузера будет сохраняться в следующих случаях.

  • Если пользователь вручную обновляет страницу.
  • Если веб-сервер становится недоступным и пользователь вынужден перезагрузить страницу, чтобы подключиться к другому серверу.

Сведения об определении шаблонов URL-адресов с помощью директивы @page см. в статье Маршрутизация ASP.NET Core Blazor и навигация.

Хранилище браузера

Для хранения временных данных, создаваемых пользователем, обычно используются коллекции браузера localStorage и sessionStorage.

  • localStorage ограничивается окном браузера. Если пользователь перезагрузит страницу или закрывается и повторно открывает браузер, состояние сохраняется. Если пользователь открывает несколько вкладок браузера, это состояние совместно используется на нескольких вкладках. Данные сохраняются в localStorage до тех пор, пока они не будут явно очищены.
  • sessionStorageобласть на вкладку браузера. Если пользователь перезагрузит вкладку, состояние сохраняется. Если пользователь закрывает вкладку или браузер, состояние теряется. Если пользователь открывает несколько вкладок браузера, каждая вкладка имеет собственную независимую версию данных.

Как правило, sessionStorage более безопасно для использования. sessionStorage позволяет избежать риска, когда пользователь открывает несколько вкладок и сталкивается со следующими проблемами.

  • Ошибки в хранилище состояний на разных вкладках.
  • Путаница в работе, когда одна вкладка перезаписывает состояние других.

localStorage лучше, если приложение должно сохранять состояние при закрытии и повторном открытии браузера.

Предостережения при использовании хранилища браузера.

  • Аналогично использованию базы данных на стороне сервера, загрузка и сохранение данных выполняются асинхронно.
  • В отличие от базы данных на стороне сервера, хранилище недоступно во время предварительной отрисовки, так как запрошенная страница не существует в браузере во время выполнения этой стадии.
  • служба хранилища нескольких килобайт данных разумно сохраняться для серверных Blazor приложений. При превышении этого порога необходимо учитывать последствия производительности, поскольку данные загружаются и сохраняются по сети.
  • Пользователи могут просматривать и изменять данные. Защита данных ASP.NET Core может снизить этот риск. Например, защищенное хранилище браузера ASP.NET Core использует защиту данных ASP.NET Core.

Сторонние пакеты NuGet предоставляют интерфейсы API для работы с localStorage и sessionStorage. Рекомендуется выбрать пакет, который прозрачно использует защиту данных ASP.NET Core. Функция защиты данных шифрует хранимые данные и уменьшает потенциальный риск несанкционированного изменения хранимых данных. Если сериализованные данные JSON хранятся в виде обычного текста, пользователи могут просматривать и изменять сохраненные данные с помощью средств разработчика в браузера. Защита данных не всегда является проблемой, так как данные могут быть тривиальными по своей природе. Например, чтение или изменение сохраненного цвета элемента пользовательского интерфейса не является серьезной угрозой безопасности для пользователя или организации. Не разрешайте пользователям проверять или изменять конфиденциальные данные.

Защищенное хранилище браузера ASP.NET Core

Защищенное хранилище браузера ASP.NET Core использует защиту данных ASP.NET Core для localStorage и sessionStorage.

Примечание.

Защищенный браузер служба хранилища использует ASP.NET Core Data Protection и поддерживается только для серверных Blazor приложений.

Предупреждение

Microsoft.AspNetCore.ProtectedBrowserStorage является неподдерживаемым экспериментальным пакетом, который не подходит для использования в рабочей среде.

Пакет можно использовать только в приложениях для ASP.NET Core 3.1.

Настройка

  1. Добавьте ссылку на пакет для Microsoft.AspNetCore.ProtectedBrowserStorage.

    Примечание.

    Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

  2. Добавьте следующий скрипт в файл _Host.cshtml до закрывающего тега </body>.

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. В Startup.ConfigureServices вызовите AddProtectedBrowserStorage, чтобы добавить службы localStorage и sessionStorage в коллекцию служб.

    services.AddProtectedBrowserStorage();
    

Сохранение и загрузка данных в компоненте

В любом компоненте, требующем загрузки или сохранения данных в хранилище браузера, используйте директиву @inject для вставки экземпляра одного из следующих компонентов.

  • ProtectedLocalStorage
  • ProtectedSessionStorage

Выбор зависит от расположения хранилища браузера, которое требуется использовать. В следующем примере используется sessionStorage.

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

Директиву @using можно поместить в файл приложения _Imports.razor, а не в компонент. Использование файла _Imports.razor делает пространство имен доступным для больших сегментов приложения или всего приложения.

Чтобы сохранить значение currentCount в компоненте Counter приложения на основе шаблона проекта Blazor, измените метод IncrementCount, чтобы использовать в нем ProtectedSessionStore.SetAsync.

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

В больших и более реалистичных приложениях хранение отдельных полей является маловероятной ситуацией. Приложения, скорее всего, будут хранить все объекты модели, включающие сложное состояние. ProtectedSessionStore автоматически сериализует и десериализует данные JSON для хранения объектов со сложными состояниями.

В предыдущем примере кода данные currentCount хранятся в виде sessionStorage['count'] в браузере пользователя. Данные не хранятся в виде обычного текста, а защищаются с помощью функции защиты данных ASP.NET Core. Зашифрованные данные можно проверить, если sessionStorage['count'] вычисляется в консоли разработчика браузера.

Чтобы данные currentCount восстанавливались, когда пользователь снова возвращается в компонент Counter (в том числе в новом канале), используйте 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");
}

Если параметры компонента включают состояние навигации, вызовите ProtectedSessionStore.GetAsync и назначьте отличный от null результат в OnParametersSetAsync, а не в OnInitializedAsync. OnInitializedAsync вызывается только один раз при первом создании экземпляра компонента. OnInitializedAsync не вызывается позже, если пользователь переходит на другой URL-адрес, оставаясь на той же странице. Дополнительные сведения см. в статье Жизненный цикл компонентов Razor ASP.NET Core.

Предупреждение

Примеры в этом разделе работают только в том случае, если на сервере не включена предварительная отрисовка. При включенной предварительной отрисовке выводится сообщение об ошибке, объясняющее, что вызовы взаимодействия с JavaScript осуществить невозможно, поскольку выполняется предварительная отрисовка компонента.

Отключите предварительную отрисовку или добавьте дополнительный код для работы с предварительной отрисовкой. Дополнительные сведения о написании кода, который работает с предварительной отрисовкой, см. в разделе Обработка предварительной отрисовки.

Обработка состояния загрузки

Так как доступ к хранилищу браузера осуществляется асинхронно через сетевое подключение, всегда есть период времени, прежде чем данные будут загружены и станут доступны для компонента. Для получения наилучших результатов отрисовка сообщения во время загрузки выполняется вместо отображения пустых или стандартных данных.

Один из подходов состоит в том, чтобы определить, равно ли значение данных null. Если это так, данные еще загружаются. В компоненте Counter по умолчанию количество хранится в int. Сделайте currentCount допускающим значение NULL, добавив вопросительный знак (?) к типу (int).

private int? currentCount;

Вместо безусловного отображения количества и кнопки Increment отображайте эти элементы только в том случае, если данные загружены, указав HasValue.

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

Обработка предварительной отрисовки

Во время предварительной отрисовки происходит следующее:

  • интерактивное подключение к браузеру пользователя не существует;
  • в браузере еще нет страницы, на которой можно запустить код JavaScript.

localStorage или sessionStorage недоступны во время предварительной отрисовки. Если компонент пытается взаимодействовать с хранилищем, выводится сообщение об ошибке, объясняющее, что вызовы взаимодействия с JavaScript осуществить невозможно, поскольку выполняется предварительная отрисовка компонента.

Одним из способов устранения этой ошибки является отключение предварительной отрисовки. Обычно это наилучший вариант, если приложение активно использует хранилище в браузере. Предварительная отрисовка увеличивает сложность и не дает приложению никаких преимуществ, так как приложение не может выдать какое-либо полезное содержимое, пока не станут доступны localStorage или sessionStorage.

Чтобы отключить предварительную отрисовку, укажите режим отрисовки с prerender параметром, заданным false для компонента самого высокого уровня в иерархии компонентов приложения, который не является корневым компонентом.

Примечание.

Создание интерактивного корневого компонента, например App компонента, не поддерживается. Поэтому предварительная подготовка не может быть отключена непосредственно компонентом App .

Для приложений на Blazor основе шаблона проекта веб-приложения предварительное создание обычно отключено, где Routes компонент используется в App компоненте (Components/App.razor):

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

Кроме того, отключите предварительную HeadOutlet отрисовку для компонента:

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

Дополнительные сведения см. в режимах отрисовки ASP.NET CoreBlazor.

Чтобы отключить предварительную отрисовку, откройте файл _Host.cshtml и измените атрибут render-modeвспомогательной функции тега компонента на Server.

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

При отключении предварительной отрисовки содержимого <head> отключается.

Предварительная отрисовка может быть полезной для других страниц, которые не используют localStorage или sessionStorage. Чтобы сохранить предварительную отрисовку, отложите операцию загрузки до тех пор, пока браузер не подключится к каналу. Ниже приведен пример хранения значения счетчика.

@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);
    }
}

Перенос сохранения состояния в общее расположение

Если многие компоненты используют хранилище на основе браузера, реализация кода поставщика состояний многократно создает дублирование кода. Одним из вариантов предотвращения дублирования кода является создание родительского компонента поставщика состояний, который инкапсулирует логику поставщика состояний. Дочерние компоненты могут работать с сохраненными данными без учета механизма сохранения состояния.

В следующем примере компонента CounterStateProvider данные счетчика сохраняются в sessionStorage.

@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 OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        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 OnInitializedAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

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

Примечание.

Дополнительные сведения о RenderFragment см. в статье Компоненты Razor ASP.NET Core.

Компонент CounterStateProvider обрабатывает этап загрузки, не выполняя отрисовку его дочернего содержимого до завершения загрузки.

Чтобы сделать состояние доступным для всех компонентов в приложении, заключите CounterStateProvider компонент вокруг Router компонента (<Router>...</Router>) в Routes компоненте с глобальной интерактивной отрисовкой на стороне сервера (интерактивный SSR).

В компоненте App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

В компоненте Routes (Components/Routes.razor):

Чтобы использовать компонент CounterStateProvider, оберните экземпляр компонента вокруг любого другого компонента, которому требуется доступ к состоянию счетчика. Чтобы сделать состояние доступным для всех компонентов в приложении, оберните CounterStateProvider компонент вокруг Router в компоненте App (App.razor).

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

Примечание.

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в статье Миграция с ASP.NET Core 3.1 на 5.0.

Упакованные компоненты получают и могут изменять состояние сохраненного счетчика. Следующий компонент Counter реализует этот шаблон.

@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)
        {
            CounterStateProvider.CurrentCount++;
            await CounterStateProvider.SaveChangesAsync();
        }
    }
}

Предыдущий компонент не требуется для взаимодействия с ProtectedBrowserStorage и не имеет отношения к этапу "загрузки".

Для работы с предварительной отрисовкой, как описано выше, CounterStateProvider можно сделать так, чтобы все компоненты, использующие данные счетчика, автоматически работали с предварительной отрисовкой. Дополнительные сведения см. в разделе Обработка предварительной отрисовки.

В целом шаблон родительского компонента поставщика состояний рекомендуется использовать в следующих случаях:

  • Для использования состояния во множестве компонентов.
  • Если имеется только один объект состояния верхнего уровня для сохранения.

Чтобы сохранить множество различных объектов состояния и использовать разные подмножества объектов в разных местах, лучше избегать глобального сохранения состояния.

Данные о состоянии пользователя, создаваемые в приложении Blazor WebAssembly, хранятся в памяти браузера.

Ниже приведены примеры данных о состоянии пользователя, хранящиеся в памяти браузера.

  • Иерархия экземпляров компонента и их последних выходных данных отрисовки в преобразованном для просмотра пользовательском интерфейсе.
  • Значения полей и свойств в экземплярах компонента.
  • Данные, хранящиеся во внедрениях зависимостей (DI) экземпляров службы.
  • Значения, заданные через вызовы взаимодействия с JavaScript.

Когда пользователь закрывает и открывает браузер или перезагружает страницу, состояние пользователя, удерживаемое в памяти браузера, теряется.

Примечание.

Защищенный браузер служба хранилища (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorageпространство имен) использует ASP.NET Core Data Protection и поддерживается только для серверных Blazor приложений.

Сохранение состояния в сеансах браузера

Как правило, состояния поддерживаются между сеансами браузера, где пользователи активно могут создавать данные, а не просто считывать уже существующие данные.

Чтобы сохранять состояние между сеансами браузера, приложение должно хранить данные не в памяти браузера, а в другом месте. Сохраняемость состояния не обеспечивается автоматически. При разработке приложения необходимо выполнить определенные действия для реализации сохраняемости данных с отслеживанием состояния.

Сохраняемость данных, как правило, требуется только для состояний высокой ценности, на создание которых пользователь затратил значительные усилия. В следующих примерах сохранение состояния экономит время или средства в коммерческих действиях.

  • Многофакторные веб-формы: требуется время для повторного ввода данных для нескольких завершенных шагов многофакторной веб-формы, если их состояние потеряно. В этом сценарии пользователь потеряет данные о своем состоянии, если он выйдет из формы и вернется в нее позже.
  • Корзины покупок: любой коммерчески важный компонент приложения, представляющего потенциальный доход, может быть сохранен. Пользователь, который теряет свое состояние, и, следовательно, свою корзину с покупками, может приобрести меньше товаров или услуг, когда вернется на сайт позже.

Приложение может сохранять только состояния приложения. Пользовательские интерфейсы не могут быть сохранены, например экземпляры компонентов и их деревья отрисовки. Компоненты и деревья отрисовки обычно не являются сериализуемыми. Чтобы сохранить состояние пользовательского интерфейса, например развернутые узлы элементов управления иерархического представления, в приложении должен быть пользовательский код для моделирования поведения этого интерфейса как сериализуемого состояния приложения.

Место сохранения состояния

Данные о состоянии могут храниться в общих расположениях:

Хранилище на стороне сервера

Для постоянного хранения данных для нескольких пользователей и устройств приложение может использовать независимое хранилище на стороне сервера, доступ к которому осуществляется через веб-API. Возможные варианты:

  • Хранилище BLOB-объектов
  • Хранилище значений ключей
  • Реляционная база данных
  • Хранилище таблиц

После сохранения данных состояние пользователя сохраняется и становится доступным во всех новых сеансах браузера.

Поскольку приложения Blazor WebAssembly полностью выполняются в браузере пользователя, им требуются дополнительные меры для доступа к защищенным внешним системам, например службам хранилища и базам данных. Защита приложений Blazor WebAssembly обеспечивается аналогично защите одностраничных приложений (SPA). Как правило, приложение выполняет проверку подлинности пользователя с помощью OAuth/OpenID Connect (OIDC), а затем взаимодействует со службами хранилища и базами данных, отправляя вызовы веб-API в серверное приложение. Приложение на стороне сервера обеспечивает перенос данных между приложением Blazor WebAssembly и службой хранилища или базой данных. Приложение Blazor WebAssembly поддерживает временное подключение к приложению на стороне сервера, в то время как приложение на стороне сервера подключено к хранилищу постоянно.

Дополнительные сведения см. на следующих ресурсах:

Дополнительные сведения о вариантах хранения данных в Azure см. здесь:

URL

Для временных данных, представляющих состояние навигации, моделируют данные как часть URL-адреса. Ниже приведены примеры данных о состоянии пользователя, которые моделируются в URL-адресе.

  • Идентификатор просматриваемой сущности.
  • Номер текущей страницы в постраничной сетке.

Если пользователь вручную перезагружает страницу, содержимое адресной строки браузера сохраняется.

Сведения об определении шаблонов URL-адресов с помощью директивы @page см. в статье Маршрутизация ASP.NET Core Blazor и навигация.

Хранилище браузера

Для хранения временных данных, создаваемых пользователем, обычно используются коллекции браузера localStorage и sessionStorage.

  • localStorage ограничивается окном браузера. Если пользователь перезагрузит страницу или закрывается и повторно открывает браузер, состояние сохраняется. Если пользователь открывает несколько вкладок браузера, это состояние совместно используется на нескольких вкладках. Данные сохраняются в localStorage до тех пор, пока они не будут явно очищены.
  • sessionStorageобласть на вкладку браузера. Если пользователь перезагрузит вкладку, состояние сохраняется. Если пользователь закрывает вкладку или браузер, состояние теряется. Если пользователь открывает несколько вкладок браузера, каждая вкладка имеет собственную независимую версию данных.

Примечание.

localStorage и sessionStorage можно использовать в приложениях Blazor WebAssembly, но только путем написания пользовательского кода или использования стороннего пакета.

Как правило, sessionStorage более безопасно для использования. sessionStorage позволяет избежать риска, когда пользователь открывает несколько вкладок и сталкивается со следующими проблемами.

  • Ошибки в хранилище состояний на разных вкладках.
  • Путаница в работе, когда одна вкладка перезаписывает состояние других.

localStorage лучше, если приложение должно сохранять состояние при закрытии и повторном открытии браузера.

Предупреждение

Пользователи могут просматривать и изменять данные, хранимые в localStorage и sessionStorage.

Служба контейнеров состояния в памяти

Вложенные компоненты обычно привязываются к данным с помощью цепочки привязки, как описано в статье Привязка к данным в ASP.NET Core Blazor. Вложенные и невложенные компоненты могут иметь общий доступ к данным с помощью зарегистрированного контейнера состояния в памяти. Пользовательский класс контейнера состояния может использовать назначаемое действие Action для уведомления компонентов в других частях приложения об изменениях состояния. В следующем примере :

  • Пара компонентов использует контейнер состояния для отслеживания свойства.
  • Один из компонентов в следующем примере вложен в другой компонент, но для использования этого подхода вложение не требуется.

Внимание

В этом разделе показано, как создать службу контейнера состояния в памяти, зарегистрировать службу и использовать службу в компонентах. Пример не сохраняет данные без дальнейшего развития. Для постоянного хранения данных контейнер состояния должен принять базовый механизм хранения, который сохраняется при очистке памяти браузера. Это можно сделать с localStorage/sessionStorage помощью или другой технологии.

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();
}

Клиентские приложения (Program файл):

builder.Services.AddSingleton<StateContainer>();

Серверные приложения (Program файл, ASP.NET Core в .NET 6 или более поздней версии):

builder.Services.AddScoped<StateContainer>();

Серверные приложения (Startup.ConfigureServices из Startup.csASP.NET Core до версии 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;
    }
}

Предыдущие компоненты реализуют IDisposable, а подписка на делегаты OnChange отменяется в методах Dispose, которые вызываются платформой при удалении компонентов. Дополнительные сведения см. в статье Жизненный цикл компонентов Razor ASP.NET Core.

Дополнительные подходы

При реализации пользовательского хранилища состояний полезный подход заключается в принятии каскадных значений и параметров:

  • Для использования состояния во множестве компонентов.
  • Если имеется только один объект состояния верхнего уровня для сохранения.

Устранение неполадок

В пользовательской службе управления состояниями обратный вызов, вызываемый вне Blazorконтекста синхронизации, должен упаковать логику обратного вызова, чтобы переместить ее в ComponentBase.InvokeAsync контекст синхронизации отрисовщика.

Если служба управления состоянием не вызывает StateHasChangedBlazorконтекст синхронизации, возникает следующая ошибка:

System.InvalidOperationException: 'The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.' (Текущий поток не связан с Dispatcher. Используйте InvokeAsync() для переключения выполнения на Dispatcher при активации отрисовки или состояния компонента).

Дополнительные сведения и пример устранения этой ошибки см. в разделе ASP.NET Отрисовка основных Razor компонентов.

Дополнительные ресурсы