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

Замечание

Это не последняя версия этой статьи. Текущий выпуск можно найти в версии этой статьи о .NET 10.

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

Эта версия ASP.NET Core больше не поддерживается. Для получения дополнительной информации см. Политику поддержки .NET и .NET Core. Текущий выпуск можно найти в версии этой статьи о .NET 10.

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

Поддержание состояния пользователя

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

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

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

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

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

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

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

Когда сохранять состояние пользователя

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

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

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

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

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

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

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

  • Регулирование вкладок браузера
  • Переключение приложений для мобильных устройств
  • Прерывания сети
  • Упреждающее управление ресурсами (приостановка неактивных каналов)
  • Улучшенная навигация

Ресурсы сервера можно освободить, если состояние канала можно сохранить, а затем возобновить позже:

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

Состояние сохраняется для двух сценариев:

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

Условия:

  • Эта функция эффективна только для рендеринга Interactive Server.
  • Если пользователь обновляет страницу (приложение), сохраняемое состояние теряется.
  • Состояние должно быть сериализуемым в формате JSON. Циклические ссылки или сущности ORM могут неправильно сериализоваться.
  • Используйте @key для уникальности при отрисовке компонентов в цикле, чтобы избежать ключевых конфликтов.
  • Сохранять только необходимое состояние. Хранение чрезмерных данных может повлиять на производительность.
  • Нет автоматической гибернации. Необходимо сначала подключиться, а затем явно настроить сохранение состояния.
  • Нет гарантии восстановления. Если сохранение состояния завершается ошибкой, приложение возвращается к стандартному отключённому режиму.

Сохраняемость состояния включена по умолчанию, когда AddInteractiveServerComponents вызывается на AddRazorComponents в файле Program. MemoryCache — это реализация хранилища по умолчанию для отдельных экземпляров приложений и сохраняет до 1000 сохраненных схем в течение двух часов, время хранения можно настроить.

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

  • PersistedCircuitInMemoryMaxRetained ({CIRCUIT COUNT} заполнитель): максимальное количество контуров, которые необходимо сохранить. По умолчанию используется 1000 каналов. Например, используйте 2000 для хранения состояния до 2000 каналов.
  • PersistedCircuitInMemoryRetentionPeriod ({RETENTION PERIOD} заполнитель): максимальный период хранения в качестве TimeSpan. Значение по умолчанию — два часа. Например, используйте TimeSpan.FromHours(3) для трехчасового периода хранения.
services.Configure<CircuitOptions>(options =>
{
    options.PersistedCircuitInMemoryMaxRetained = {CIRCUIT COUNT};
    options.PersistedCircuitInMemoryRetentionPeriod = {RETENTION PERIOD};
});

Сохранение состояния компонента в схемах основано на существующем PersistentComponentState API, который продолжает сохранять состояние для предварительно отрисованных компонентов, переходящих в интерактивный режим отрисовки. Дополнительные сведения см. в разделе ASP.NET Core Blazor сохраняемость предопределенного состояния.

[ПРИМЕЧАНИЕ] Сохранение состояния компонента для предварительной отрисовки работает для любого интерактивного режима отрисовки, но сохранение состояния контура работает только в режиме отрисовки Interactive Server.

Аннотируйте свойства компонента public посредством атрибута [PersistentState], чтобы включить сохранение состояния схемы. В следующем примере элементы идентифицируются с помощью атрибута директивы @key, чтобы обеспечить уникальный идентификатор для каждого экземпляра компонента.

@foreach (var item in Items)
{
    <ItemDisplay @key="@($"unique-prefix-{item.Id}")" Item="item" />
}

@code {
    [PersistentState]
    public List<Item> Items { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Items ??= await LoadItemsAsync();
    }
}

Чтобы сохранить состояние для сервиса с ограниченной областью видимости:

  • Аннотируйте public свойство службы с помощью атрибута [PersistentState].
  • Добавьте службу в коллекцию служб.
  • Вызовите метод расширения RegisterPersistentService для службы.
public class CustomUserService
{
    [PersistentState]
    public string UserData { get; set; }
}

services.AddScoped<CustomUserService>();

services.AddRazorComponents()
  .AddInteractiveServerComponents()
  .RegisterPersistentService<CustomUserService>(RenderMode.InteractiveAuto);

[ПРИМЕЧАНИЕ] В предыдущем примере сохраняется состояние UserData, когда служба используется в предварительной подготовке компонентов как для интерактивного сервера, так и для интерактивной отрисовки WebAssembly, так как RenderMode.InteractiveAuto указано в RegisterPersistentService. Однако сохраняемость состояния схемы доступна только для режима отрисовки интерактивного сервера.

Чтобы обрабатывать сохраняемость распределенного состояния (и выступать в качестве механизма сохраняемости состояния по умолчанию при настройке), назначьте приложению значение HybridCache (API: HybridCache): который настраивает собственный период сохраняемости (PersistedCircuitDistributedRetentionPeriodпо умолчанию восемь часов). HybridCache используется, так как он предоставляет единый подход к распределенном хранилищу, который не требует отдельных пакетов для каждого поставщика хранилища.

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

services.AddHybridCache()
    .AddRedis("{CONNECTION STRING}");

services.AddRazorComponents()
    .AddInteractiveServerComponents();

В предыдущем примере заполнитель {CONNECTION STRING} представляет строку подключения кэш Redis, которую следует предоставить с помощью безопасного подхода, например, Secret Manager в среде Development или Azure Key Vault с Azure управляемыми удостоверениями для приложений, развернутых в Azure, в любой среде.

Дополнительные сведения об атрибуте [PersistentState] attribute см. в разделе ASP.NET Core Blazor сохранение предварительно отрисованного состояния.

Приостановка и возобновление цепей

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

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

Из обработчика событий JavaScript:

  • Вызвать Blazor.pauseCircuit(), чтобы приостановить цепь.
  • ** Позвоните Blazor.resumeCircuit(), чтобы возобновить цепь.

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

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    Blazor.pauseCircuit();
  } else if (document.visibilityState === 'visible') {
    Blazor.resumeCircuit();
  }
});

Приостановка цепи, инициируемая сервером

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

Эта функция полезна в следующих сценариях:

  • Запланированные отключения и развертывания.
  • Очистка экземпляров.
  • Окна обслуживания приложений.

Circuit.RequestCircuitPauseAsync(CancellationToken) используется для запроса того, чтобы подключенный клиент начал процедуру корректной приостановки канала. Запрос CancellationToken отменяется до его принятия платформой. Метод возвращает true, если запрос был принят и клиенту было предложено перейти к приостановке.

Когда серверное Blazor приложение завершает работу (например, во время развертывания), подключенные клиенты теряют свое SignalR подключение. Подход в этом разделе:

  • Обнаруживает завершение работы, прежде чем сервер закрывает подключения.
  • Активирует паузу на подключенных каналах через Microsoft.AspNetCore.Components.Server.Circuits.Circuit.RequestCircuitPauseAsync.
  • Сохраняет состояние с помощью [PersistentState] атрибута в свойствах компонента.

В следующем примере реализации следующие файлы кода помещаются в папку в Shutdown корне приложения:

  • CircuitShutdownService.cs: одноэлементная служба, которая координирует завершение работы.
  • ShutdownCircuitHandler.cs: служба с областью действия для каждого активного канала.
  • ShutdownCircuitOptions.cs: параметры конфигурации.

Shutdown/ShutdownCircuitOptions.cs:

namespace PauseResumeOnShutdown.Shutdown;

public class ShutdownCircuitOptions
{
    public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(10);
}

При использовании следующего подхода тот факт, что код отправляет RequestCircuitPauseAsync асинхронно, не означает, что к моменту возврата значения клиент уже приостановлен. Это только запрос на приостановку клиента, который клиент может отложить. Вот почему код содержит TaskCompletionSource (_shutdownTcs), который задаётся, когда не подключена ни одна цепь (все они успешно отключены). Если клиент запрашивает отсрочку на срок, превышающий допустимый сервером, дольше, чем на ShutdownTimeout, клиент не сохраняет своё состояние и сталкивается с обычным разрывом соединения. Другие клиенты, которые не откладывают запрос на приостановку, восстанавливают свои подключения после того, как приложение снова выходит в сеть при сохранённом состоянии.

Shutdown/CircuitShutdownService.cs:

using System.Collections.Concurrent;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;

namespace PauseResumeOnShutdown.Shutdown;

public class CircuitShutdownService
{
    private readonly ConcurrentDictionary<string, Circuit> 
        _circuits = new();
    private readonly ShutdownCircuitOptions _options;
    private bool _isShuttingDown;
    private TaskCompletionSource _shutdownTcs = new();

    public CircuitShutdownService(IHostApplicationLifetime appLifetime, 
        IOptions<ShutdownCircuitOptions> options)
    {
        _options = options.Value;
        appLifetime.ApplicationStopping.Register(OnApplicationStopping);
    }

    private void OnApplicationStopping()
    {
        _isShuttingDown = true;

        if (_circuits.IsEmpty)
        {
            return;
        }

        var pauseTasks = _circuits.Values
            .Select(c => c.RequestCircuitPauseAsync().AsTask())
            .Append(_shutdownTcs.Task);

        Task.WhenAll(pauseTasks).Wait(_options.ShutdownTimeout);

        _shutdownTcs.Task.Wait(_options.ShutdownTimeout);
    }

    public void Register(string circuitId, Circuit circuit)
    {
        _circuits.TryAdd(circuitId, circuit);
    }

    public void Unregister(string circuitId)
    {
        _circuits.TryRemove(circuitId, out _);

        if (_isShuttingDown && _circuits.IsEmpty)
        {
            _shutdownTcs.TrySetResult();
        }
    }
}

Shutdown/ShutdownCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

namespace PauseResumeOnShutdown.Shutdown;

public class ShutdownCircuitHandler(CircuitShutdownService shutdownService) 
    : CircuitHandler
{
    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        shutdownService.Register(circuit.Id, circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        shutdownService.Unregister(circuit.Id);

        return Task.CompletedTask;
    }
}

Program.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
using PauseResumeOnShutdown.Components;
using PauseResumeOnShutdown.Shutdown;

var builder = WebApplication.CreateBuilder(args);

// Increase host shutdown timeout to allow time for pause operations
// Must be greater than `ShutdownTimeout` in `ShutdownCircuitOptions` 
// otherwise the host terminates connections before circuits finish 
// pausing
builder.Host.ConfigureHostOptions(options =>
    options.ShutdownTimeout = TimeSpan.FromSeconds(30));

builder.Services.Configure<ShutdownCircuitOptions>(options =>
    options.ShutdownTimeout = TimeSpan.FromSeconds(10));

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

// Register CircuitShutdownService as a singleton
builder.Services.AddSingleton<CircuitShutdownService>();

// Register ShutdownCircuitHandler as a scoped CircuitHandler
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, ShutdownCircuitHandler>());

var app = builder.Build();

// ... rest of pipeline

При необходимости, чтобы отложить приостановку на клиенте до завершения критически важных операций (например, платежа, находящегося в обработке), настройте функцию обратного вызова onPauseRequested в конфигурации запуска Blazor. Поместите следующее после ссылки на скрипт на стороне Blazor сервера:

<script> 
  Blazor.start({
    circuit: {
      onPauseRequested: async () => {
        // Perform any critical cleanup or wait for in-flight operations.
        // Return true to allow the pause or false to reject it.
        return true;
      }
    }
  });
</script>

Без обратного onPauseRequested вызова клиент приостанавливается немедленно, когда сервер запрашивает его.

В компоненте используйте [PersistentState] атрибут для сохранения состояния компонента во время приостановки и возобновления работы. В следующем Counter примере компонента текущее число (CurrentCount) сохраняется во время перезапуска сервера с помощью предыдущего подхода:

@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [PersistentState]
    public int CurrentCount { get; set; }

    private void IncrementCount()
    {
        CurrentCount++;
    }
}

Дополнительные сведения об атрибуте [PersistentState] attribute см. в разделе ASP.NET Core Blazor сохранение предварительно отрисованного состояния.

-->

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

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

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

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

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

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

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

Данные могут храниться временно или постоянно в сценариях на стороне сервера.

Сохраняемость временных данных

Для сохранения временных данных между HTTP-запросами во время статической серверной отрисовки Blazor поддерживает TempData. TempData идеально подходит для таких сценариев, как флэш-сообщения после отправки формы, передача данных во время перенаправления (шаблон POST-Redirect-GET) и однократные уведомления.

TempData:

  • Доступен, когда AddRazorComponents вызывается в файле Program приложения.
  • Предоставляется как каскадное значение с использованием атрибута [CascadingParameter] или атрибута параметра [SupplyParameterFromTempData].
  • Доступ осуществляется по ключу (строке).
  • Поддерживает примитивы, DateTime, перечисления, Guid, и коллекции (массивы, List<T>, Dictionary<TKey,TValue>).
  • Хранит object? значения, требуя приведения среды выполнения (например: var message = TempData["Message"] as string). IntelliSense и проверка типов не поддерживаются.
  • Использует нечувствительные ключи регистра, поэтому TempData["message"] и TempData["Message"] извлекают то же значение.
[CascadingParameter]
public ITempData? TempData { get; set; }

Если атрибут передаётся параметру для простого чтения/записи одного значения, используйте атрибут [SupplyParameterFromTempData] без ключа или с ключом (строкой):

[SupplyParameterFromTempData]
public string? Message { get; set; }

[SupplyParameterFromTempData(Name = "flash_message")]
public string? FlashMessage { get; set; }

Интерфейс ITempData предоставляет следующие методы для управления жизненным циклом значений:

  • Get: получает значение, связанное с указанным ключом, и планирует данные для удаления.
  • Peek: возвращает значение, связанное с указанным ключом, без маркировки данных для удаления.
  • Keep: помечает все ключи в словаре для хранения. Значения доступны в следующем запросе.
  • Keep(string): помечает указанный ключ (строка) для хранения. Значение доступно в следующем запросе.

Данные, хранящиеся в TempData, автоматически удаляются после считывания, если Keep/Keep(string) не вызывается или данные не передаются через Peek.

Поставщик по умолчанию на основе cookie использует Защиту данных для шифрования.

Вызовите AddCookieTempDataValueProvider в коллекции служб в файле приложения Program, передав CookieTempDataProviderOptions для изменения параметров cookie в следующей таблице.

Параметр API Значение по умолчанию
Имя Name .AspNetCore.Components.TempData
Только HTTP HttpOnly true
Значение SameSite SameSite SameSiteMode.Strict
Безопасная политика SecurePolicy CookieSecurePolicy.Always

Пример (задает значения по умолчанию):

builder.Services.AddRazorComponents(options =>
{
    options.TempDataCookie.Name = ".AspNetCore.Components.TempData";
    options.TempDataCookie.HttpOnly = true;
    options.TempDataCookie.SameSite = SameSiteMode.Strict;
    options.TempDataCookie.SecurePolicy = CookieSecurePolicy.Always;
});

Поддерживаются только сериализуемые в ФОРМАТЕ JSON примитивы и коллекции. Определяемые пользователем классы и сериализация пользовательских объектов не поддерживаются. Blazor WebAssembly и Blazor Server не поддерживаются.

В качестве альтернативы по умолчанию для SessionStorageTempDataProvider, доступен CookieTempDataProvider. Использование cookie и одновременное хранение сеансов не поддерживается.

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

  • Требуется явная конфигурация состояния сеанса.
  • Не имеет практических ограничений размера (в пределах ограничений сеанса).
  • Требуется сопоставление сеансов (сеансы с привязкой) в средах с балансировкой нагрузки. Без этого пользователи могут потерять данные. Дополнительные сведения см. в разделе ASP.NET Core BlazorSignalR руководство.

Конфигурация хранилища сеансов:

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();

builder.Services.Configure<RazorComponentsServiceOptions>(options =>
{
    options.TempDataProviderType = TempDataProviderType.SessionStorage;
});

...

app.UseSession();

Браузеры применяют ограничение размера 4 КБ cookie . TempData автоматически использует ChunkingCookieManager для разделения файлов cookie на несколько cookie заголовков, но разработчики, хранящие большое количество данных, должны переключиться на хранилище сеансов, что требует привязки к сеансу.

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

Pages/TempDataExample1.razor:

@page "/tempdata-example-1"
@inject NavigationManager NavigationManager

<p>@message</p>

<form method="post" @formname="SetMessage" @onsubmit="Submit">
    <AntiforgeryToken />
    <button type="submit">Submit</button>
</form>

@code {
    [CascadingParameter]
    public ITempData? TempData { get; set; }

    private string? message;

    protected override void OnInitialized()
    {
        // Get removes the value after reading (one-time use)
        message = TempData?.Get("Message") as string ?? "No message";
    }

    private void Submit()
    {
        TempData!["Message"] = "Form submitted successfully!";
        NavigationManager.NavigateTo("/tempdata-example-1", forceLoad: true);
    }
}

Чтение без удаления (Peek):

protected override void OnInitialized()
{
    var notification = TempData?.Peek("Message") as string;
}

Сохраните определенное значение для другого запроса (Keep(string)):

protected override void OnInitialized()
{
    var message = TempData?.Get("Message") as string;
    TempData?.Keep("Message");
}

Сохраните все значения для другого запроса (Keep):

protected override void OnInitialized()
{
    TempData?.Keep();
}

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

Pages/TempDataExample2.razor:

@page "/tempdata-example-2"
@inject NavigationManager NavigationManager

<p>@Message</p>

<form method="post" @formname="SetMessage" @onsubmit="Submit">
    <AntiforgeryToken />
    <button type="submit">Submit</button>
</form>

@code {
    [SupplyParameterFromTempData]
    public string? Message { get; set; }

    private void Submit()
    {
        Message = "Form submitted successfully!";
        NavigationManager.NavigateTo("/tempdata-example-2", forceLoad: true);
    }
}

Сохраняемость постоянных данных

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

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

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

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

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

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

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