Управление состоянием ASP.NET Core Blazor
Примечание
Это не последняя версия этой статьи. В текущем выпуске см. эту статью в версии .NET 9.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см. версию этой статьи .NET 9.
Важно!
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске смотрите версию этой статьи для .NET 9.
В этой статье описываются распространенные методики сохранения данных пользователя (состояния) во время использования приложения и во время выполнения сеансов браузера.
Примечание
Примеры кода в этой статье используют nullable reference types (NRTs) и статический анализ состояния null компилятора .NET, которые поддерживаются в ASP.NET Core в .NET 6 или более поздних версиях. При нацеливании на ASP.NET Core 5.0 или более раннюю версию удалите обозначение типа null (?
) из типов в примерах из статьи.
Серверная часть Blazor — это платформа приложений с отслеживанием состояния. В большинстве случаев приложение поддерживает подключение к серверу. Состояние пользователя хранится в памяти сервера в схеме.
Примеры состояния пользователя, хранящегося в контуре, включают:
- Иерархия экземпляров компонентов и их последних результатов отрисовки в отображенном пользовательском интерфейсе.
- Значения полей и свойств в экземплярах компонента.
- Данные, хранящиеся в экземплярах службы внедрения зависимостей (DI), областью действия которых является контур.
Данные о состоянии пользователя также можно найти в переменных JavaScript в памяти браузера, заданной с помощью вызовов взаимодействия с JavaScript.
Если пользователь испытывает временный сбой сетевого подключения, Blazor пытается повторно подключить пользователя к исходному каналу с сохранением исходного состояния. Однако повторное подключение пользователя к исходному каналу в памяти сервера не всегда возможно.
- Сервер не может постоянно хранить отключенную цепь. Сервер должен освободить отключенную цепь после истечения времени ожидания или при нехватке памяти на сервере.
- В средах развертывания с несколькими серверами и балансировкой нагрузки отдельные серверы могут выйти из строя или быть автоматически удалены, если они больше не требуются для обработки общего объема запросов. В этом случае исходные запросы на обработку на сервере могут стать недоступными для пользователя, когда он попытается подключиться повторно.
- Пользователь может закрыть и повторно открыть браузер или перезагрузить страницу, которая удаляет любое состояние, удерживающееся в памяти браузера. Например, теряются значения переменных JavaScript, заданные через вызовы взаимодействия с JavaScript.
Если пользователь не может повторно подключиться к исходному каналу, он получает новый канал с пустым состоянием. Это эквивалентно закрытию и повторному открытию классического приложения.
Как правило, состояния поддерживаются между контурами, где пользователи активно создают данные, а не просто считывают уже существующие данные.
Чтобы сохранять состояние при смене каналов, приложение должно хранить данные не в памяти сервера, а в другом месте. Сохраняемость состояния не обеспечивается автоматически. При разработке приложения необходимо предпринять шаги для реализации сохранения состояния данных.
Сохраняемость данных, как правило, требуется только для состояний высокой ценности, на создание которых пользователь затратил значительные усилия. В следующих примерах сохранение состояния либо экономит время, либо способствует коммерческой деятельности.
- Многошаговые веб-формы: пользователю требуется время на повторный ввод данных, если состояние нескольких завершенных шагов потеряно. В этом сценарии пользователь потеряет состояние, если покинет форму и вернется к ней позже.
- Корзины покупок: любой коммерчески важный компонент приложения, представляющего потенциальный доход, может быть сохранен. Пользователь, который теряет свое состояние, и, следовательно, свою корзину с покупками, может приобрести меньше товаров или услуг, когда вернется на сайт позже.
Приложение может сохранять только состояния приложения. Пользовательские интерфейсы не могут быть сохранены, например экземпляры компонентов и их деревья отрисовки. Компоненты и деревья отрисовки обычно не являются сериализуемыми. Для сохранения состояния пользовательского интерфейса, например развернутых узлов элементов управления деревьевидного представления, приложение должно использовать пользовательский код для моделирования поведения этого интерфейса как сериализуемого состояния приложения.
Данные о состоянии могут храниться в общих расположениях:
- Хранилище на стороне сервера
- URL-адрес
- Хранилище браузера
- Служба хранения состояния в памяти
- Каскадные значения и параметры
Для постоянного хранения данных для нескольких пользователей и устройств приложение может использовать хранилище на стороне сервера. Возможные варианты:
- Хранилище BLOB-объектов
- Хранилище значений ключей
- Реляционная база данных
- Хранилище таблиц
После сохранения данных состояние пользователя сохраняется и становится доступным во всех новых каналах.
Дополнительные сведения о вариантах хранения данных в Azure см. здесь:
Для временных данных, представляющих состояние навигации, моделируют данные как часть URL-адреса. Ниже приведены примеры данных о состоянии пользователя, которые моделируются в URL-адресе.
- Идентификатор просматриваемой сущности.
- Номер текущей страницы в сетке, разбитой на страницы.
Содержимое адресной строки браузера будет сохраняться в следующих случаях.
- Если пользователь вручную обновляет страницу.
- Если веб-сервер становится недоступным и пользователь вынужден перезагрузить страницу, чтобы подключиться к другому серверу.
Сведения об определении шаблонов URL-адресов с помощью директивы @page
см. в статье Маршрутизация ASP.NET Core Blazor и навигация.
Для хранения временных данных, создаваемых пользователем, обычно используются коллекции браузера localStorage
и sessionStorage
.
-
localStorage
распространяется на экземпляр браузера. Если пользователь перезагрузит страницу или закрывается и повторно открывает браузер, состояние сохраняется. Если пользователь открывает несколько вкладок браузера, состояние между ними будет общим. Данные сохраняются в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 для localStorage
и sessionStorage
.
Примечание
Защищенное хранилище браузеров использует ASP.NET Core Data Protection и поддерживается только для серверных Blazor приложений.
Предупреждение
Microsoft.AspNetCore.ProtectedBrowserStorage
является неподдерживаемым экспериментальным пакетом, который не подходит для использования в рабочей среде.
Пакет можно использовать только в приложениях для ASP.NET Core 3.1.
Добавьте ссылку на пакет для
Microsoft.AspNetCore.ProtectedBrowserStorage
.Примечание
Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.
Добавьте следующий скрипт в файл
_Host.cshtml
до закрывающего тега</body>
.<script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
В
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 Web App, где компонент 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
. Компонент обрабатывает этап загрузки, не отображая дочернее содержимое до завершения загрузки состояния.
Компонент CounterStateProvider
занимается предварительной отрисовкой, не загружая состояние до тех пор, пока не произойдёт отрисовка компонента в методе жизненного цикла OnAfterRenderAsync
, который не выполняется во время предварительной отрисовки.
Подход в этом разделе не может вызвать перерисовку нескольких подписанных компонентов на одной странице. Если один подписанный компонент изменяет состояние, он перерисовывается и может отображать обновлённые данные, но другой компонент на той же странице, отображающий это состояние, отображает устаревшие данные до тех пор, пока не произойдёт его собственная следующая перерисовка. Поэтому подход, описанный в этом разделе, лучше всего подходит для использования состояния в одном компоненте на странице.
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);
}
}
Примечание
Дополнительные сведения о RenderFragment см. в статье Компоненты Razor ASP.NET Core.
Чтобы сделать состояние доступным для всех компонентов в приложении, оберните компонент CounterStateProvider
вокруг компонента Router (<Router>...</Router>
) в компоненте Routes
с использованием глобальной интерактивной серверной отрисовки (interactive 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)
{
await CounterStateProvider.IncrementCount();
}
}
}
Предыдущий компонент не требуется для взаимодействия с ProtectedBrowserStorage
и не имеет отношения к этапу "загрузки".
В целом шаблон родительского компонента поставщика состояний рекомендуется использовать в следующих случаях:
- Для потребления состояния во множестве компонентов.
- Если имеется только один объект состояния верхнего уровня для сохранения.
Чтобы сохранить множество различных объектов состояния и использовать разные подмножества объектов в разных местах, лучше избегать глобального сохранения состояния.
Данные о состоянии пользователя, создаваемые в приложении Blazor WebAssembly, хранятся в памяти браузера.
Ниже приведены примеры данных о состоянии пользователя, хранящиеся в памяти браузера.
- Иерархия экземпляров компонентов и их последние результаты отрисовки в отрисованном пользовательском интерфейсе.
- Значения полей и свойств в экземплярах компонента.
- Данные, хранящиеся во внедрениях зависимостей (DI) экземпляров службы.
- Значения, заданные посредством вызовов интеропа с JavaScript.
Когда пользователь закрывает и открывает браузер или перезагружает страницу, состояние пользователя, удерживаемое в памяти браузера, теряется.
Примечание
Защищенное хранилище браузеров (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage пространство имен) зависит от ASP.NET Core Data Protection и поддерживается только для серверных Blazor приложений.
Как правило, поддерживайте состояния между сеансами браузера в тех случаях, когда пользователи активно создают данные, а не просто просматривают уже существующие данные.
Чтобы сохранять состояние между сеансами браузера, приложение должно хранить данные не в памяти браузера, а в другом месте. Сохраняемость состояния не обеспечивается автоматически. При разработке приложения необходимо выполнить определенные действия для реализации сохраняемости данных с отслеживанием состояния.
Сохраняемость данных, как правило, требуется только для состояний высокой ценности, на создание которых пользователь затратил значительные усилия. В следующих примерах сохранение состояния либо экономит время, либо способствует коммерческой деятельности.
- Многошаговые веб-формы: пользователю требуется время для повторного ввода данных после нескольких завершенных шагов многошаговой веб-формы, если их состояние потеряно. В этом сценарии пользователь потеряет своё состояние, если он выйдет из формы и вернется в нее позже.
- Корзины покупок: любой коммерчески важный компонент приложения, представляющего потенциальный доход, может быть сохранен. Пользователь, который теряет свое состояние, и, следовательно, свою корзину с покупками, может приобрести меньше товаров или услуг, когда вернется на сайт позже.
Приложение может сохранять только состояния приложения. Пользовательские интерфейсы не могут быть сохранены, такие как экземпляры компонентов и их деревья рендеринга. Компоненты и деревья отрисовки обычно не являются сериализуемыми. Чтобы сохранить состояние пользовательского интерфейса, например развернутые узлы элементов управления иерархического представления, в приложении должен быть пользовательский код для моделирования поведения этого интерфейса как сериализуемого состояния приложения.
Состояние может сохраняться в распространённых местах.
- Хранилище на стороне сервера
- URL-адрес
- Хранилище браузера
- Служба управления состоянием в памяти
- Каскадные значения и параметры
Для постоянного хранения данных для нескольких пользователей и устройств приложение может использовать независимое хранилище на стороне сервера, доступ к которому осуществляется через веб-API. Возможные варианты:
- BLOB-хранилище
- Хранилище значений ключей
- Реляционная база данных
- Хранилище таблиц
После сохранения данных состояние пользователя сохраняется и становится доступным во всех новых сеансах браузера.
Поскольку приложения Blazor WebAssembly полностью выполняются в браузере пользователя, им требуются дополнительные меры для доступа к защищенным внешним системам, например службам хранилища и базам данных. Защита приложений Blazor WebAssembly обеспечивается аналогично защите одностраничных приложений (SPA). Как правило, приложение выполняет проверку подлинности пользователя с помощью OAuth/OpenID Connect (OIDC), а затем взаимодействует со службами хранилища и базами данных, отправляя вызовы веб-API в серверное приложение. Приложение на стороне сервера обеспечивает перенос данных между приложением Blazor WebAssembly и службой хранилища или базой данных. Приложение Blazor WebAssembly поддерживает временное подключение к приложению на стороне сервера, в то время как приложение на стороне сервера подключено к хранилищу постоянно.
Дополнительные сведения см. на следующих ресурсах:
- Вызов веб-API в приложении ASP.NET CoreBlazor
- Защита ASP.NET Core Blazor WebAssembly
- Blazor Статьи по безопасности и Identity
Дополнительные сведения о вариантах хранения данных в Azure см. здесь:
Для временных данных, представляющих состояние навигации, моделируют данные как часть URL-адреса. Ниже приведены примеры данных о состоянии пользователя, которые моделируются в URL-адресе.
- Идентификатор просматриваемой сущности.
- Номер текущей страницы в постраничной сетке.
Если пользователь вручную перезагружает страницу, содержимое адресной строки браузера сохраняется.
Сведения об определении шаблонов URL-адресов с помощью директивы @page
см. в статье Маршрутизация ASP.NET Core Blazor и навигация.
Для хранения временных данных, создаваемых пользователем, обычно используются коллекции браузера localStorage
и sessionStorage
.
-
localStorage
распространяется на экземпляр браузера. Если пользователь перезагрузит страницу или закрывается и повторно открывает браузер, состояние сохраняется. Если пользователь открывает несколько вкладок браузера, состояние вкладок делится между ними. Данные сохраняются в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.cs
ASP.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
, которые вызываются фреймворком при удалении компонентов. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.
Используйте каскадные значения и параметры для управления состоянием путем потоков данных из компонента предка Razor в компоненты-потомки.
Каскадные значения корневого уровня с CascadingValueSource<TValue> разрешают Razor уведомления подписчика компонента об измененных каскадных значениях. Дополнительные сведения и рабочий пример см. в примере NotifyingDalek
ASP.NET Core Blazor каскадных значений и параметров.
При реализации пользовательского хранилища состояний полезный подход заключается в принятии каскадных значений и параметров:
- Для потребления состояния во множестве компонентов.
- Если имеется только один объект состояния верхнего уровня для сохранения.
При использовании пользовательской службы управления состояниями, в которой требуется поддерживать изменения состояния вне контекста синхронизации Blazor(например, из таймера или фоновой службы), все используемые компоненты должны упаковать вызов StateHasChanged в ComponentBase.InvokeAsync. Это гарантирует, что уведомление об изменении обрабатывается в контексте синхронизации отрисовщика.
Если служба управления состоянием не вызывает StateHasChanged в контексте синхронизации объекта Blazor, возникает следующая ошибка:
System.InvalidOperationException: 'Текущий поток не связан с диспетчером.' Используйте InvokeAsync(), чтобы переключить выполнение на Dispatcher при активации рендеринга или состояния компонента.
Дополнительные сведения и пример устранения этой ошибки см. в статье
- Сохранение состояния приложения перед операцией проверки подлинности (Blazor WebAssembly)
- Управление состоянием с помощью API внешнего сервера
Отзыв о ASP.NET Core
ASP.NET Core — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв: