Поделиться через


Предварительный рендеринг компонентов ASP.NET Core Razor

Примечание.

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

Внимание

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

В текущем выпуске смотрите версию .NET 9 этой статьи.

В этой статье описываются сценарии предварительной отрисовки для компонентов, отрисованных на сервере в Razors.

Предварительная отрисовка — это процесс первоначальной отрисовки содержимого страницы на сервере без включения обработчиков событий для отрисованных элементов управления. Сервер выводит HTML-интерфейс страницы как можно скорее в ответ на первоначальный запрос, что делает приложение более адаптивным к пользователям. Предварительная подготовка также может улучшить оптимизацию поисковой системы (SEO), отрисовав содержимое для первоначального HTTP-ответа, используемого поисковыми системами для вычисления ранжирования страниц.

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

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

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

PrerenderedCounter1.razor:

@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

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

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

@code {
    private int currentCount;

    protected override void OnInitialized()
    {
        currentCount = Random.Shared.Next(100);
        Logger.LogInformation("currentCount set to {Count}", currentCount);
    }

    private void IncrementCount() => currentCount++;
}

Запустите приложение и проверьте журналирование компонента. Ниже приведен пример выходных данных.

Примечание.

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

info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92

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

Чтобы удерживать начальное значение счетчика во время предварительной отрисовки, Blazor поддерживает сохранение состояния на предварительно отрисованных страницах с помощью службы PersistentComponentState (а для компонентов, внедренных в страницы или представления Razor приложений Pages или MVC, — с помощью вспомогательного тега Persist Component State).

Чтобы сохранить предварительно созданное состояние, используйте [SupplyParameterFromPersistentComponentState] атрибут для сохранения состояния в свойствах. Свойства с этим атрибутом автоматически сохраняются с помощью службы PersistentComponentState при предпросмотре. Состояние извлекается при интерактивном рендеринге компонента или при создании экземпляра службы.

По умолчанию свойства сериализуются с помощью сериализатора System.Text.Json с параметрами по умолчанию. Сериализация не является безопасной и требует сохранения используемых типов. Дополнительные сведения см. в статье Настройка средства обрезки для ASP.NET Core Blazor.

В следующем примере показан общий шаблон, в котором {TYPE} заполнитель представляет тип сохраняемых данных.

@code {
    [SupplyParameterFromPersistentComponentState]
    public {TYPE} Data { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Data ??= await ...;
    }
}

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

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@inject ILogger<PrerenderedCounter2> Logger

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

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

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

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

    protected override void OnInitialized()
    {
        CurrentCount ??= Random.Shared.Next(100);
        Logger.LogInformation("CurrentCount set to {Count}", CurrentCount);
    }

    private void IncrementCount() => CurrentCount++;
}

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

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

PersistentChild.razor:

<div>
    <p>Current count: @Element.CurrentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

@code {
    [SupplyParameterFromPersistentComponentState]
    public State Element { get; set; }

    protected override void OnInitialized()
    {
        Element ??= new State();
    }

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

    private class State
    {
        public int CurrentCount { get; set; }
    }
}

Parent.razor:

@page "/parent"

@foreach (var element in elements)
{
    <PersistentChild @key="element.Name" />
}

В следующем примере, который сериализует состояние службы:

  • Свойства, аннотированные атрибутом [SupplyParameterFromPersistentComponentState] , сериализуются во время предварительной отрисовки и десериализации, когда приложение становится интерактивным.
  • Метод AddPersistentService используется для регистрации службы для сохраняемости. Режим отрисовки является обязательным, так как режим отрисовки не может быть выведен из типа службы. Используйте любое из следующих значений:
    • RenderMode.Server: Услуга доступна для режима рендеринга Интерактивного Сервера.
    • RenderMode.Webassembly: Услуга доступна для режима интерактивной отрисовки WebAssembly.
    • RenderMode.InteractiveAuto: служба доступна для режимов отрисовки интерактивного сервера и интерактивного вебассембли, если компонент отрисовывается в любом из этих режимов.
  • Служба определяется во время инициализации интерактивного режима рендеринга, а свойства, аннотированные атрибутом [SupplyParameterFromPersistentComponentState], десериализируются.

Примечание.

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

CounterService.cs:

public class CounterService
{
    [SupplyParameterFromPersistentComponentState]
    public int CurrentCount { get; set; }

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

В Program.cs:

builder.Services.AddPersistentService<CounterService>(RenderMode.InteractiveAuto);

Сериализованные свойства определяются из фактического экземпляра службы:

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

В качестве альтернативы использованию декларативной модели для сохранения состояния с [SupplyParameterFromPersistentComponentState] атрибутом можно использовать PersistentComponentState службу напрямую, которая обеспечивает большую гибкость для сложных сценариев сохраняемости состояния. Вызовите PersistentComponentState.RegisterOnPersisting, чтобы зарегистрировать обратный вызов для сохранения состояния компонента во время предварительного рендеринга. Состояние извлекается при интерактивном отображении компонента. Выполните вызов в конце кода инициализации, чтобы избежать потенциального условия гонки при завершении работы приложения.

В следующем примере показан общий шаблон:

  • Заполнитель {TYPE} представляет тип сохраняемых данных.
  • Заполнитель {TOKEN} — это строка идентификатора состояния. Рекомендуется использовать nameof({VARIABLE}), где {VARIABLE} заполнитель — это имя переменной, содержащей состояние. Использование nameof() для идентификатора состояния позволяет избежать использования кавычек.
@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
    private {TYPE} data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<{TYPE}>(
            "{TOKEN}", out var restored))
        {
            data = await ...;
        }
        else
        {
            data = restored!;
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("{TOKEN}", data);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

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

PrerenderedCounter3.razor:

@page "/prerendered-counter-3"
@implements IDisposable
@inject ILogger<PrerenderedCounter3> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 3</PageTitle>

<h1>Prerendered Counter 3</h1>

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

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

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    private void IncrementCount() => currentCount++;

    void IDisposable.Dispose() => persistingSubscription.Dispose();
}

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

В следующем примере показан общий шаблон:

  • Заполнитель {TYPE} представляет тип сохраняемых данных.
  • Заполнитель {TOKEN} — это строка идентификатора состояния. Рекомендуется использовать nameof({VARIABLE}), где {VARIABLE} заполнитель — это имя переменной, содержащей состояние. Использование nameof() для идентификатора состояния позволяет избежать использования кавычек.
@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
    private {TYPE} data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<{TYPE}>(
            "{TOKEN}", out var restored))
        {
            data = await ...;
        }
        else
        {
            data = restored!;
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("{TOKEN}", data);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

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

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

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

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

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();

    private void IncrementCount() => currentCount++;
}

При выполнении компонента currentCount устанавливается только один раз во время предварительного рендеринга. Значение восстанавливается при повторной отрисовке компонента. Ниже приведен пример выходных данных.

Примечание.

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

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96

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

Сохраняемое предварительно созданное состояние передается клиенту, где оно используется для восстановления состояния компонента. Во время рендеринга на стороне клиента (CSR InteractiveWebAssembly) данные доступны в браузере и не должны содержать конфиденциальную информацию. Во время интерактивной отрисовки на стороне сервера (интерактивная служба SSR) InteractiveServerASP.NET Core Data Protection гарантирует безопасное передачу данных. Режим рендеринга InteractiveAuto объединяет взаимодействие WebAssembly и сервера, поэтому необходимо внимательно оценить возможность воздействия данных на браузер, как и в случае с CSR.

Компоненты, встроенные в страницы и представления (Razor Pages/MVC)

Для компонентов, встроенных на страницу или представление Razor в приложении Pages или MVC, необходимо добавить тег- помощник Persist Component State, включив HTML-тег <persist-component-state /> внутрь закрывающего тега </body> макета приложения. Это необходимо только для Razor приложений Pages и MVC. Для получения дополнительной информации см. раздел Вспомогательная функция Tag для сохранения состояния компонента в ASP.NET Core.

Pages/Shared/_Layout.cshtml:

<body>
    ...

    <persist-component-state />
</body>

Интерактивная маршрутизация и предварительная отрисовка

Если компонент Routes не определяет режим отрисовки, приложение использует интерактивность и навигацию по страницам или компонентам. При использовании навигации по страницам или компонентам, внутренняя навигация осуществляется с помощью расширенной маршрутизации после того, как приложение становится интерактивным. †внутренняя в этом контексте означает, что URL-адрес события навигации является Blazor конечной точкой внутри приложения.

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

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

Если интерактивный контур уже установлен и осуществляется улучшенная навигация к странице, использующей состояние сохраняемого компонента, состояние недоступно в уже имеющемся контуре для его использования компонентом. Для внутреннего запроса страницы нет пререндеринга, а служба PersistentComponentState не знает, что произошла улучшенная навигация. Нет механизма доставки обновлений состояния компонентам, которые уже работают в существующем канале. Причиной этого является то, что Blazor поддерживает только передачу состояния с сервера клиенту во время инициализации среды выполнения, а не после запуска среды выполнения.

Дополнительная работа над платформой Blazor для решения этого сценария рассматривается для .NET 10 (ноябрь 2025 г.). Дополнительные сведения и обсуждение сообщества по поводу неподдерживаемых обходных решенийсм. в разделе Поддержка неизменного состояния компонента при расширенной навигации страницами (dotnet/aspnetcore #51584). .Неподдерживаемые обходные пути не санкционированы корпорацией Майкрософт для использования в Blazor приложениях. использовать сторонние пакеты, подходы и код по вашему собственному риску.

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

Руководство по предварительной отрисовке

Руководство по предварительной Blazor отрисовке организовано в документации по темам. Следующие ссылки охватывают все рекомендации по предварительной отрисовке во всей документации по тематике.