Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Примечание.
Это не последняя версия этой статьи. Для текущей версии см. версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. Для текущей версии см. версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Для текущей версии см. версию .NET 9 этой статьи.
В этой статье приведены сведения о жизненном цикле компонента Razor ASP.NET Core, а также о том, как использовать события жизненного цикла.
События жизненного цикла
Компонент Razor обрабатывает события жизненного цикла компонента Razor в наборе синхронных и асинхронных методов жизненного цикла. Методы жизненного цикла можно переопределить для выполнения дополнительных операций с компонентами во время инициализации и отрисовки компонента.
Эта статья упрощает обработку событий жизненного цикла компонентов, чтобы уточнить сложную логику платформы и не охватывает все изменения, внесенные в течение многих лет. Возможно, вам потребуется получить доступ к справочному источнику, чтобы интегрировать пользовательскую обработку событий с обработкой событий жизненного цикла ComponentBase
. Комментарии кода в справочном источнике включают дополнительные замечания по обработке событий жизненного цикла, которые не отображаются в этой статье или в документации по API.
Примечание.
Ссылки в документации на исходный код .NET обычно ведут на основную ветвь репозитория, которая отражает текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного выпуска, используйте раскрывающееся меню Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).
На следующих упрощенных схемах показана Razor обработка событий жизненного цикла компонентов. Методы C#, связанные с событиями жизненного цикла, с примерами приводятся в следующих разделах этой статьи.
События жизненного цикла компонента
- Если компонент выполняет отрисовку в первый раз по запросу:
- Создайте экземпляр компонента.
- Выполните внедрение свойств.
- Вызовите
OnInitialized{Async}
. Если возвращается неполное Task, ожидается Task, и затем компонент перерисовывается. Синхронный метод вызывается до асинхронного метода.
- Вызовите
OnParametersSet{Async}
. Если возвращается неполное Task, ожидается Task, и затем компонент перерисовывается. Синхронный метод вызывается до асинхронного метода. - Рендеринг для всей синхронной работы и выполнение операций класса Task.
Примечание.
Асинхронные действия, выполняемые в событиях жизненного цикла, могут отложить отрисовку компонентов или отображение данных. Дополнительные сведения см. в разделе Обработка неполных асинхронных действий при отрисовке далее в этой статье.
Родительский компонент рендерится перед дочерними элементами, так как именно рендеринг определяет, какие из них присутствуют. Если используется синхронная инициализация родительского компонента, то родительская инициализация гарантированно завершится первой. Если используется асинхронная инициализация родительского компонента, порядок завершения инициализации родительского и дочернего компонента не может быть определен, так как он зависит от выполняемого кода инициализации.
Обработка событий DOM:
- Выполняется обработка событий.
- Если возвращается неполное Task, ожидается Task, и затем компонент перерисовывается.
- Рендеринг для всей синхронной работы и выполнение операций класса Task.
Жизненный цикл Render
- Избегайте дальнейших операций отрисовки компонента при выполнении обоих следующих условий:
- Это не первый рендер.
-
ShouldRender
возвращаетfalse
.
- Выполните сборку отличий дерева отрисовки и отрисуйте компонент.
- Подождите обновления DOM.
- Вызовите
OnAfterRender{Async}
. Синхронный метод вызывается перед асинхронным методом.
Вызовы разработчика к StateHasChanged
приводят к повторному рендерингу. Дополнительные сведения см. в статье Отрисовка компонентов Razor ASP.NET Core.
Состояние покоя во время пререндеринга
В серверных приложениях Blazor предварительная отрисовка ожидает стабилизации, что означает, что компонент не рендерится, пока все компоненты в дереве отрисовки не завершат отрисовку. Пассивность может привести к заметным задержкам при отрисовке, когда компонент выполняет длительные задачи во время инициализации и других методов жизненного цикла, что приводит к плохому пользовательскому опыту. Дополнительные сведения см. в разделе Обработка неполных асинхронных действий при отрисовке далее в этой статье.
После указания параметров (SetParametersAsync
)
SetParametersAsync задает параметры, предоставляемые родительским элементом компонента в дереве отрисовки или из параметров маршрута.
Параметр ParameterView метода содержит набор значений параметров для компонента при каждом вызове SetParametersAsync. Переопределяя метод SetParametersAsync, код разработчика может напрямую взаимодействовать с параметрами ParameterView.
Реализация SetParametersAsync по умолчанию устанавливает значения каждого свойства, используя атрибут [Parameter]
или [CascadingParameter]
атрибут, имеющий соответствующее значение в ParameterView. Параметры, у которых нет соответствующего значения в ParameterView, остаются неизменными.
Как правило, код должен вызывать метод базового класса (await base.SetParametersAsync(parameters);
) при переопределении SetParametersAsync. В расширенных сценариях код разработчика может интерпретировать значения входящих параметров каким-либо образом, не вызывая метод базового класса. Например, назначать входящие параметры свойствам класса не обязательно. Однако при структурировании кода необходимо обращаться к ComponentBase
справочному источнику, избегая вызова метода базового класса, так как он вызывает другие методы жизненного цикла и запускает процесс рендеринга сложным способом.
Примечание.
Ссылки в документации на исходный код .NET обычно ведут на основную ветвь репозитория, которая отражает текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного выпуска, используйте раскрывающееся меню Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Если вы хотите полагаться на ComponentBase.SetParametersAsync логику инициализации и отрисовки, но не обрабатывать входящие параметры, у вас есть возможность передать пустой ParameterView в метод базового класса.
await base.SetParametersAsync(ParameterView.Empty);
Если обработчики событий предоставляются в коде разработчика, отключите их при удалении. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.
В следующем примере ParameterView.TryGetValue присваивает Param
значение параметраvalue
, если анализ параметра маршрута для Param
выполнен успешно. Если value
не равно null
, значение отображается компонентом.
Хотя сопоставление параметров маршрута не учитывает регистр, в шаблоне маршрута совпадают только те имена параметров, которые учитывают регистр. В следующем примере, чтобы получить значение с помощью метода /{Param?}
, вам потребуется использовать в шаблоне маршрута TryGetValue, а не /{param?}
. Если в этом сценарии используется /{param?}
, функция TryGetValue возвращает значение false
, а message
не задается в качестве строкового параметра message
.
SetParamsAsync.razor
:
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async"
@page "/set-params-async/{Param}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
Инициализация компонента (OnInitialized{Async}
)
OnInitialized и OnInitializedAsync используется исключительно для инициализации компонента в течение всего времени существования экземпляра компонента. Значения параметров и изменения значений параметров не должны влиять на инициализацию, выполняемую в этих методах. Например, загрузка статических параметров в раскрывающийся список, который не изменяется в течение всего времени существования компонента и который не зависит от значений параметров, выполняется в одном из этих методов жизненного цикла. Если значения параметров или изменения значений параметров влияют на состояние компонента, используйте OnParametersSet{Async}
вместо этого.
Эти методы вызываются при инициализации компонента после получения его начальных параметров в SetParametersAsync. Синхронный метод вызывается перед асинхронным методом.
Если используется синхронная инициализация родительского компонента, родительская инициализация гарантированно завершится до инициализации дочернего компонента. Если используется асинхронная инициализация родительского компонента, порядок завершения инициализации родительского и дочернего компонента не может быть определен, так как он зависит от выполняемого кода инициализации.
Для синхронной операции переопределите OnInitialized:
OnInit.razor
:
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
Чтобы выполнить асинхронную операцию, переопределите OnInitializedAsync и используйте оператор await
:
protected override async Task OnInitializedAsync()
{
await ...
}
Если пользовательский базовый класс используется с пользовательской логикой инициализации, вызовите OnInitializedAsync на базовом классе.
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
Не обязательно вызывать ComponentBase.OnInitializedAsync, если не используется настраиваемый базовый класс с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".
Компонент должен удостовериться, что находится в допустимом состоянии для рендеринга, когда OnInitializedAsync ожидает Task, которое может быть неполным. Если метод возвращает неполный Task, часть метода, завершающаяся синхронно, должна оставить компонент в допустимом состоянии для отрисовки. Дополнительные сведения см. во вводных замечаниях по контексту синхронизации ASP.NET Core Blazor и удалению компонентов ASP.NET Core Razor.
Приложения Blazor, которые рендерят свое содержимое на сервере, вызывают OnInitializedAsyncдважды:
- Один раз, когда компонент изначально статически рендерится как часть страницы.
- Во второй раз, когда браузер рендерит компонент.
Чтобы узнать, как предотвратить двойное выполнение кода разработчика в OnInitializedAsync при выполнении предварительной отрисовки, см. раздел Повторное подключение с отслеживанием состояния после предварительной отрисовки. Содержимое раздела посвящено Blazor Web App и состоянию SignalRповторного подключения. Чтобы сохранить состояние в процессе выполнения кода инициализации при предварительной обработке, см. в разделе Предварительная обработка компонентов ASP.NET CoreRazor.
Чтобы узнать, как предотвратить двойное выполнение кода разработчика в OnInitializedAsync при выполнении предварительной отрисовки, см. раздел Повторное подключение с отслеживанием состояния после предварительной отрисовки. Хотя содержимое в разделе посвящено Blazor Server устойчивому повторному подключениюSignalR, сценарий предварительной подготовки в размещенных Blazor WebAssembly решениях (WebAssemblyPrerendered) включает аналогичные условия и подходы для предотвращения двойного выполнения кода разработчика. Сведения о сохранении состояния во время выполнения кода инициализации во время предварительной подготовки см. в статье "Интеграция компонентов ASP.NET Core Razor с MVC или Razor Pages".
Когда приложение Blazor выполняет предварительную отрисовку, некоторые действия, такие как вызов в JavaScript (взаимодействие с JS), невозможны. Компоненты могут рендериться иначе при предварительном рендеринге. Дополнительные сведения см. в разделе Предварительная отрисовка с помощью взаимодействия с JavaScript.
Если обработчики событий предоставлены в коде разработчика, отключите их при освобождении ресурсов. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.
Используйте потоковую отрисовку со статической отрисовкой на стороне сервера (статический SSR) или предварительной подготовкой, чтобы улучшить пользовательский опыт компонентов, выполняющих длительные асинхронные задачи для OnInitializedAsync полной отрисовки. Дополнительные сведения см. на следующих ресурсах:
- обрабатывать неполные асинхронные действия при отрисовке (эта статья)
-
ASP.NET компонента Core
После указания параметров (OnParametersSet{Async}
)
OnParametersSet или OnParametersSetAsync называются:
После инициализации компонента в OnInitialized или OnInitializedAsync.
Когда родительский компонент перерендерится и передает:
- Известные или примитивные неизменяемые типы, в которых изменился по крайней мере один параметр.
- Параметры сложных типов. Платформа не может определить, изменились ли значения параметров сложного типа на внутреннем уровне, поэтому при наличии одного или нескольких параметров сложных типов она рассматривает набор параметров как измененный.
Дополнительные сведения о правилах рендеринга см. в статье Отрисовка компонентов Razor ASP.NET Core.
Синхронный метод вызывается раньше асинхронного метода.
Методы можно вызывать, даже если значения параметров не изменились. Это поведение подчеркивает необходимость реализации дополнительной логики разработчиками в методах для проверки того, изменились ли значения параметров перед повторной инициализацией данных или состояний, зависящих от этих параметров.
В следующем примере компонента перейдите на страницу компонента по URL-адресу:
- В котором указана дата начала, полученная
StartDate
:/on-parameters-set/2021-03-19
- В котором не указана дата начала, и где
StartDate
присваивается значение текущего местного времени:/on-parameters-set
Примечание.
В маршруте компонента невозможно одновременно ограничить параметр DateTime и применить ограничение маршрута datetime
, а также сделать параметр необязательным. Поэтому следующий компонент OnParamsSet
для управления маршрутизацией в URL-адресе с заданным сегментом даты и без него использует две директивы @page
.
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
Асинхронная работа при применении параметров и значений свойств должна происходить во время события жизненного цикла OnParametersSetAsync:
protected override async Task OnParametersSetAsync()
{
await ...
}
Ели используется пользовательский базовый класс с пользовательской логикой инициализации, вызовите OnParametersSetAsync на базовом классе:
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
Не обязательно вызывать ComponentBase.OnParametersSetAsync, если не используется настраиваемый базовый класс с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".
Компонент должен удостовериться, что находится в допустимом состоянии для рендеринга, когда OnParametersSetAsync ожидает Task, которое может быть неполным. Если метод возвращает неполный Task, часть метода, завершающаяся синхронно, должна оставить компонент в допустимом состоянии для отрисовки. Для получения дополнительной информации см. вступительные замечания по контексту синхронизации ASP.NET Core Blazor и удалению компонентов ASP.NET Core Razor.
Если обработчики событий предоставляются в коде разработчика, удалите их при утилизации. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.
Если одноразовый компонент не использует CancellationToken, OnParametersSet и OnParametersSetAsync должны проверить, утилизирован ли компонент. Если OnParametersSetAsync возвращает неполный Task, компонент должен убедиться, что часть метода, которая выполняется синхронно, оставляет компонент в корректном состоянии для рендеринга. Дополнительные сведения см. во введении по контексту синхронизации ASP.NET Core Blazor и утилизации компонентов ASP.NET Core Razor.
Дополнительные сведения о параметрах маршрута и ограничениях см. в статье Маршрутизация ASP.NET Core Blazor и навигация.
Пример реализации SetParametersAsync
вручную для повышения производительности в некоторых сценариях см. в разделе ASP.NET CoreBlazor: лучшие практики по производительности.
После отрисовки компонента (OnAfterRender{Async}
)
OnAfterRender и OnAfterRenderAsync вызываются после интерактивной отрисовки компонента и после того, как пользовательский интерфейс завершил обновление (например, после добавления элементов в DOM браузера). На этом этапе заполняются ссылки на элементы и компоненты. Используйте этот этап, чтобы выполнить дополнительные инициализирующие шаги с отрисованным содержимым, такие как JS вызовы взаимодействия, работающие с отрисованными элементами DOM. Синхронный метод вызывается до асинхронного метода.
Эти методы не вызываются во время предварительной отрисовки или отрисовки на стороне сервера (статический SSR), так как эти процессы не подключены к DOM в динамическом браузере и уже завершены до обновления DOM.
Для OnAfterRenderAsync этого компонент не выполняет автоматическую отрисовку после завершения любого возвращаемого Task
, чтобы избежать бесконечного цикла отрисовки.
OnAfterRender и OnAfterRenderAsync вызываются после завершения отрисовки компонента. На этом этапе заполняются ссылки на элементы и компоненты. Используйте этот этап, чтобы выполнить дополнительные шаги инициализации отрисованного содержимого, например, вызовы взаимодействия, которые работают с отрисованными элементами DOM. Сначала вызывается синхронный метод, затем асинхронный метод.
Эти методы не вызываются во время предварительной отрисовки, так как предварительная отрисовка не присоединена к DOM в динамическом браузере и уже завершена до обновления DOM.
Для OnAfterRenderAsync этого компонент не выполняет автоматическую отрисовку после завершения любого возвращаемого Task
, чтобы избежать бесконечного цикла отрисовки.
Параметр firstRender
для OnAfterRender и OnAfterRenderAsync:
- Значение устанавливается в
true
при первом рендеринге экземпляра компонента. - Может использоваться, чтобы гарантировать однократное выполнение инициализации.
AfterRender.razor
:
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
В AfterRender.razor
примере выводятся следующие данные в консоль при загрузке страницы и выборе кнопки:
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
Асинхронная работа, которая выполняется сразу после рендеринга, должна происходить во время события жизненного цикла OnAfterRenderAsync.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Если используется пользовательский базовый класс с пользовательской логикой инициализации, вызовите OnAfterRenderAsync на базовом классе.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
Не обязательно вызывать ComponentBase.OnAfterRenderAsync, если не используется настраиваемый базовый класс с пользовательской логикой. Дополнительные сведения см. в разделе "Методы жизненного цикла базового класса".
Даже если вы возвращаете Task из OnAfterRenderAsync, фреймворк не планирует дополнительный цикл рендеринга для вашего компонента после завершения задачи. Это позволяет избежать бесконечного цикла отрисовки. Это поведение отличается от других методов жизненного цикла, которые планируют дальнейший цикл отрисовки после завершения возвращаемой операции Task.
OnAfterRender и OnAfterRenderAsyncне вызываются при предварительной отрисовке на сервере. Методы вызываются, когда компонент отрисовывается в интерактивном режиме после предварительной отрисовки. При предварительной отрисовке приложения происходит следующее.
- Компонент выполняется на сервере для создания статической HTML-разметки в HTTP-ответе. На этом этапе OnAfterRender и OnAfterRenderAsync не вызываются.
-
Blazor При запуске скрипта (
blazor.{server|webassembly|web}.js
) в браузере компонент перезапускается в интерактивном режиме отрисовки. После перезапуска компонента вызываются методы OnAfterRender и OnAfterRenderAsync, так как приложение больше не находится на этапе предварительной отрисовки.
Если обработчики событий предоставляются в коде разработчика, отключите их при удалении. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.
Методы жизненного цикла базового класса
При переопределении методов жизненного цикла Blazor, не требуется вызывать методы жизненного цикла базового класса для ComponentBase. Однако компонент должен вызывать переопределенный метод жизненного цикла базового класса в следующих ситуациях:
- При переопределении ComponentBase.SetParametersAsync, обычно вызывается
await base.SetParametersAsync(parameters);
, потому что метод базового класса вызывает другие методы жизненного цикла и сложным образом запускает отрисовку. Дополнительные сведения см. в разделе "Настройка параметров (SetParametersAsync
) - Если метод базового класса содержит логику, которую необходимо выполнить. Потребители библиотек обычно вызывают методы жизненного цикла базового класса при наследовании базового класса, так как базовые классы библиотек часто имеют пользовательскую логику жизненного цикла для выполнения. Если приложение использует базовый класс из библиотеки, ознакомьтесь с документацией по библиотеке.
В следующем примере base.OnInitialized();
вызывается для обеспечения выполнения метода базового класса OnInitialized
. Без вызова BlazorRocksBase2.OnInitialized
не будет выполнено.
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
Изменения состояния (StateHasChanged
)
StateHasChanged уведомляет компонент о том, что его состояние изменилось. При необходимости вызов StateHasChanged помещает в очередь повторный рендеринг, который выполняется, когда основной поток приложения свободен.
StateHasChanged вызывается автоматически для методов EventCallback. Дополнительные сведения об обратных вызовах событий см. в разделе Обработка событий Blazor в ASP.NET Core.
Дополнительные сведения об отрисовке компонентов и о том, когда лучше вызывать StateHasChanged и когда для этого следует использовать ComponentBase.InvokeAsync, см. в статье Отрисовка компонента Razor ASP.NET Core.
Обрабатывать неполные асинхронные действия при отрисовке
Асинхронные действия, выполняемые в рамках событий жизненного цикла, могут не завершиться до момента отрисовки компонента. Во время выполнения метода жизненного цикла объекты могут быть null
или заполнены данными не полностью. Предоставьте логику отрисовки для подтверждения инициализации объектов. Отображайте элементы пользовательского интерфейса заполнителя (например, сообщение загрузки), пока объекты находятся в состоянии null
.
В следующем компоненте Slow
OnInitializedAsync переопределяется для асинхронного выполнения длительной задачи. Пока isLoading
находится в состоянии true
, пользователю отображается сообщение о загрузке. После завершения Task
, возвращенного OnInitializedAsync, компонент перерендеривается с обновленным состоянием, отображая сообщение "Finished!
".
Slow.razor
:
@page "/slow"
<h2>Slow Component</h2>
@if (isLoading)
{
<div><em>Loading...</em></div>
}
else
{
<div>Finished!</div>
}
@code {
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
isLoading = false;
}
private Task LoadDataAsync()
{
return Task.Delay(10000);
}
}
Предыдущий компонент использует переменную isLoading
для отображения сообщения о загрузке. Аналогичный подход используется для компонента, который загружает данные в коллекцию и проверяет, является ли коллекция null
для отображения сообщения о загрузке. В следующем примере проверяется коллекция movies
для null
, чтобы отобразить сообщение о загрузке или отобразить коллекцию фильмов:
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
@* display movies *@
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovies();
}
}
Предварительное отображение ожидает состояния покоя , что означает, что компонент не отрисовывается, пока не завершится отрисовка всех компонентов в дереве рендеринга. Это означает, что сообщение о загрузке не отображается, пока метод OnInitializedAsync дочернего компонента выполняет длинную задачу во время предварительной подготовки. Чтобы продемонстрировать это поведение, поместите предыдущий компонент Slow
в компонент Home
тестового приложения:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Slow />
Примечание.
Хотя в примерах этого раздела рассматривается метод жизненного цикла OnInitializedAsync, другие методы жизненного цикла, выполняемые во время предварительной подготовки, могут отложить окончательную отрисовку компонента. Только OnAfterRender{Async}
не выполняется во время предподготовки и не подвержен задержкам из-за инерции.
Во время предварительной подготовки компонент Home
не отображается до отрисовки компонента Slow
, который занимает десять секунд. Пользовательский интерфейс пуст в течение этого десяти секундного периода, и сообщение о загрузке отсутствует. После предварительного рендеринга компонент Home
отображается, и показывается сообщение о загрузке компонента Slow
. Через десять секунд компонент Slow
, наконец, отображает готовое сообщение.
Как показано в приведенной выше демонстрации, покой во время предварительного рендеринга приводит к плохому пользовательскому опыту. Чтобы улучшить взаимодействие с пользователем, начните с реализации потоковой отрисовки, чтобы избежать ожидания завершения асинхронной задачи в процессе предварительной подготовки.
Добавьте атрибут [StreamRendering]
в компонент Slow
(используйте [StreamRendering(true)]
в .NET 8):
@attribute [StreamRendering]
Когда компонент Home
предварительно отображается, компонент Slow
быстро отображается с его сообщением о загрузке. Компонент Home
не ожидает 10 секунд, пока компонент Slow
завершит отрисовку. Однако готовое сообщение, отображаемое в конце предварительной отрисовки, заменяется сообщением о загрузке, пока компонент окончательно отрисовывается, что добавляет ещё десять секунд задержки. Это происходит, так как компонент Slow
выполняется дважды, а также LoadDataAsync
выполняется дважды. Когда компонент обращается к ресурсам, таким как службы и базы данных, двойное выполнение вызовов служб и запросов к базам данных создает нежелательную нагрузку на ресурсы приложения.
Чтобы устранить двойную отрисовку сообщения о загрузке и повторное выполнение вызовов службы и базы данных, сохраните предварительно созданное состояние с PersistentComponentState для окончательной отрисовки компонента, как показано в следующих обновлениях компонента Slow
:
@page "/slow"
@attribute [StreamRendering]
<h2>Slow Component</h2>
@if (Data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@Data</div>
}
@code {
[SupplyParameterFromPersistentComponentState]
public string? Data { get; set; }
protected override async Task OnInitializedAsync()
{
Data ??= await LoadDataAsync();
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState
<h2>Slow Component</h2>
@if (data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@data</div>
}
@code {
private string? data;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
{
data = await LoadDataAsync();
}
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(nameof(data), data);
return Task.CompletedTask;
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}
Объединяя потоковую отрисовку с состоянием постоянного компонента:
- Службам и базам данных требуется только один вызов инициализации компонентов.
- Компоненты быстро отображают свои интерфейсы, показывая сообщения о загрузке во время выполнения длительных задач для наилучшего взаимодействия с пользователем.
Дополнительные сведения см. на следующих ресурсах:
-
ASP.NET компонента Core - Prerender компонентов ASP.NET Core Razor.
::moniker-end
Покой во время пререндера приводит к плохому пользовательскому опыту. Задержка может быть устранена в приложениях, ориентированных на .NET 8 или более поздние версии, с использованием функции потоковой отрисовки, обычно в сочетании с сохранением состояния компонента во время предварительной отрисовки, чтобы избежать ожидания завершения асинхронной задачи. В версиях .NET до 8.0 выполнение долгосрочной фоновой задачи, которая загружает данные после окончательной отрисовки, может устранить длительную задержку отрисовки, вызванную простоем.
Обработка ошибок
Сведения об обработке ошибок во время выполнения метода жизненного цикла см. в статье Обработка ошибок в приложениях ASP.NET Core Blazor.
Восстановление подключения с сохранением состояния после предварительного рендеринга
При предварительной подготовке на сервере компонент изначально отображается статически как часть страницы. После того как браузер установит соединение SignalR с сервером, компонент будет визуализирован снова и станет интерактивным. Если метод жизненного цикла OnInitialized{Async}
для инициализации компонента присутствует, он выполняется дважды:
- Когда компонент предварительно рендерится статически.
- После установления соединения с сервером.
Это может привести к заметному изменению данных, отображаемых в пользовательском интерфейсе, когда компонент отрисовывается окончательно. Чтобы избежать этого поведения, передайте идентификатор для сохранения состояния в кэше во время предварительной отрисовки и получайте это состояние после предварительной отрисовки.
В следующем коде показано, как WeatherForecastService
позволяет избежать изменения отображения данных из-за предварительной отрисовки. Ожидаемый Delay (await Task.Delay(...)
) имитирует короткую задержку перед возвратом данных метода GetForecastAsync
.
Добавьте службы с AddMemoryCache в коллекцию служб в файле Program
приложения:
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
Дополнительные сведения о RenderMode см. в разделе руководства ASP.NET Core BlazorSignalR.
В этом разделе основное внимание уделяется Blazor Web App повторным подключениямSignalR с отслеживанием состояния. Чтобы сохранить состояние в процессе выполнения кода инициализации при предварительной обработке, см. в разделе Предварительная обработка компонентов ASP.NET CoreRazor.
Хотя содержимое этого раздела посвящено Blazor Server подключению с сохранением состоянияSignalR, сценарий предварительной отрисовки в хостинговых Blazor WebAssembly решениях (WebAssemblyPrerendered) включает аналогичные состояния и подходы, чтобы предотвратить повторное выполнение кода разработчика. Сведения о сохранении состояния во время выполнения кода инициализации во время предварительной подготовки см. в статье "Интеграция компонентов ASP.NET Core Razor с MVC или Razor Pages".
Предварительная отрисовка с помощью взаимодействия с JavaScript
Этот раздел относится к серверным приложениям, которые предварительно рендерят Razor компоненты. Предварительная отрисовка рассматривается в Razor Prerender ASP.NET Core.
Примечание.
Внутренняя навигация для интерактивной маршрутизации в Blazor Web Apps не включает запрос нового содержимого страницы с сервера. Поэтому предварительное отображение не выполняется для внутренних запросов страниц. Если приложение выбирает интерактивную маршрутизацию, выполните полную перезагрузку страницы для примеров компонентов, демонстрирующих поведение предварительной отрисовки. Дополнительные сведения см. в разделе "Предварительная обработка компонентов ASP.NET CoreRazor".
Этот раздел относится к серверным приложениям и размещенным приложениям, которые предварительно отрисовывают Blazor WebAssemblyRazor компоненты. Предварительная подготовка рассматривается в Razor.
Во время предварительного рендеринга вызов JavaScript (JS) невозможен. В следующем примере показано, как использовать интероперабельность JS в рамках логики инициализации компонента таким образом, который совместим с пререндерингом.
Следующая функция scrollElementIntoView
:
- Прокручивается с помощью
scrollIntoView
до переданного элемента. - Возвращает значение свойства элемента
top
изgetBoundingClientRect
метода.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
При вызове IJSRuntime.InvokeAsync функции JS в коде компонента ElementReference используется только в OnAfterRenderAsync и не в каких-либо ранних методах жизненного цикла, так как до отрисовки компонента отсутствует элемент HTML DOM.
StateHasChanged
(ссылочный источник) вызывается для постановки в очередь перерисовки компонента с новым состоянием, полученным из вызова JS взаимодействия между платформами (дополнительные сведения см. в разделе рендеринга компонентов Razor ASP.NET Core). Бесконечный цикл не создается, так как StateHasChanged вызывается только когда scrollPosition
равно null
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
В предыдущем примере глобальная функция загрязняет клиента. Более эффективный подход для приложений в рабочей среде приведен в разделе Изоляция JavaScript в модулях JavaScript.
Отменяемая фоновая работа
Компоненты часто выполняют длительные фоновые операции, например осуществление сетевых вызовов (HttpClient) и взаимодействие с базами данных. В целях экономии системных ресурсов в ряде ситуаций желательно отключить выполнение фоновых операций. Например, фоновые асинхронные операции не останавливаются автоматически, когда пользователь выходит из компонента.
Ниже перечислены другие причины, по которым может потребоваться отмена фоновых рабочих элементов.
- Выполнение фоновой задачи было начато с ошибочными входными данными или параметрами обработки.
- Текущий набор выполняемых фоновых рабочих элементов должен быть заменен новым набором.
- Необходимо изменить приоритет выполняемых в данный момент задач.
- Чтобы выполнить повторное развертывание на сервере, завершите работу приложения.
- Ресурсы сервера становятся ограниченными, в связи с чем возникает необходимость в пересмотре графика выполнения фоновых рабочих элементов.
Чтобы реализовать механизм отменяемой фоновой операции в компоненте, выполните следующие действия.
- Используйте CancellationTokenSource и CancellationToken.
- При удалении компонента и в любое время, когда требуется отмена, вручную сбросьте токен, вызовите
CancellationTokenSource.Cancel
для сигнала о том, что фоновая работа должна быть отменена. - После возврата асинхронного вызова вызовите ThrowIfCancellationRequested на токене.
В следующем примере :
-
await Task.Delay(10000, cts.Token);
представляет долго выполняемую асинхронную фоновую операцию. -
BackgroundResourceMethod
представляет длительный фоновый метод, который не должен запускаться, еслиResource
удаляется перед вызовом метода.
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
Чтобы отобразить индикатор загрузки во время фоновой работы, используйте следующий подход.
Создайте компонент индикатора загрузки с параметром Loading
, который может отображать дочернее содержимое в объекте RenderFragment. Для параметра Loading
:
- Когда
true
, отображайте индикатор загрузки. - Когда
false
, отрисовывайте содержимое компонента (ChildContent
). Дополнительные сведения см. в разделе Фрагменты рендеринга дочернего контента.
ContentLoading.razor
:
@if (Loading)
{
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public bool Loading { get; set; }
}
Чтобы загрузить стили CSS для индикатора, добавьте стили внутрь содержимого <head>
, используя компонент HeadContent. Дополнительные сведения см. в статье Управление содержимым head в приложениях ASP.NET Core Blazor.
@if (Loading)
{
<!-- OPTIONAL ...
<HeadContent>
<style>
...
</style>
</HeadContent>
-->
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
...
Оберните разметку компонента Razor компонентом ContentLoading
и передайте значение из поля C# в параметр Loading
при выполнении процесса инициализации компонентом.
<ContentLoading Loading="@loading">
...
</ContentLoading>
@code {
private bool loading = true;
...
protected override async Task OnInitializedAsync()
{
await LongRunningWork().ContinueWith(_ => loading = false);
}
...
}
События повторного подключения Blazor Server
События жизненного цикла компонентов, описанные в этой статье, работают отдельно от обработчиков событий повторного подключения на стороне сервера. Когда соединение с клиентом SignalR потеряно, прерываются только обновления пользовательского интерфейса. Обновления интерфейса возобновляются при восстановлении подключения. Дополнительные сведения о конфигурации и событиях обработчика канала см. в статье Руководство по ASP.NET Core BlazorSignalR.
Дополнительные ресурсы
Обработать перехваченные исключения вне жизненного цикла Razor компонента
ASP.NET Core