注意
此版本不是本文的最新版本。 对于当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅本文的 .NET 9 版本。
本文介绍 ASP.NET Core Razor 组件生命周期以及如何使用生命周期事件。
生命周期事件
Razor 组件处理一组同步和异步生命周期方法中的 Razor 组件生命周期事件。 可以替代生命周期方法,以在组件初始化和呈现期间对组件执行其他操作。
本文简化了组件生命周期事件处理,以阐明复杂的框架逻辑,其中并不涵盖多年来所做的每项更改。 可能需要访问 ComponentBase
引用源,以将自定义事件处理与 Blazor 的生命周期事件处理集成。 引用源中的代码注释包括有关未出现在本文或 API 文档中的生命周期事件处理的其他注释。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
以下简化图展示了 Razor 组件生命周期事件处理。 本文以下部分中的示例定义了与生命周期事件关联的 C# 方法。
组件生命周期事件:
- 如果组件是第一次呈现在请求上:
- 创建组件的实例。
- 执行属性注入。
- 调用
OnInitialized{Async}
。 如果返回不完整的 Task,则将等待 Task,然后重新呈现组件。 同步方法是在异步方法之前调用的。
- 调用
OnParametersSet{Async}
。 如果返回不完整的 Task,则将等待 Task,然后重新呈现组件。 同步方法是在异步方法之前调用的。 - 呈现所有同步工作和完整的 Task。
注意
在生命周期事件中执行的异步操作可能会延迟组件呈现或显示数据。 如需更多信息,请参阅本文后面的处理呈现时的不完整异步操作部分。
父组件之所以在其子组件之前进行呈现,是因为呈现决定了哪些子组件会存在。 如果使用同步父组件初始化,则保证先完成父组件初始化。 如果使用异步父组件初始化,则无法确定父组件和子组件初始化的完成顺序,因为它取决于正在运行的初始化代码。
DOM 事件处理:
Render
生命周期:
- 如果同时满足以下两个条件,请避免对组件执行进一步的呈现操作:
- 它不是第一个呈现。
-
ShouldRender
返回false
。
- 生成呈现树差异并呈现组件。
- 等待 DOM 更新。
- 调用
OnAfterRender{Async}
。 同步方法是在异步方法之前调用的。
开发人员调用 StateHasChanged
会产生重新呈现。 有关详细信息,请参阅 ASP.NET Core Razor 组件呈现。
预呈现期间静止
在服务器端 Blazor 应用中,预渲染等待 静默,这意味着只有在渲染树中的所有组件完成渲染之后,单个组件才会被渲染。 当组件在初始化和其他生命周期方法中执行长时间运行的任务期间,静止状态可能会导致渲染明显延迟,进而导致糟糕的用户体验。 如需更多信息,请参阅本文后面的处理呈现时的不完整异步操作部分。
设置参数时 (SetParametersAsync
)
SetParametersAsync 设置由组件的父组件在呈现树或路由参数中提供的参数。
每次调用 ParameterView 时,方法的 参数都包含该组件的SetParametersAsync值集。 通过重写 SetParametersAsync 方法,开发人员代码可以直接与 ParameterView 参数交互。
SetParametersAsync 的默认实现使用 [Parameter]
或 [CascadingParameter]
特性(在 ParameterView 中具有对应的值)设置每个属性的值。 在 ParameterView 中没有对应值的参数保持不变。
通常,重写 await base.SetParametersAsync(parameters);
时,你的代码应调用基类的函数 (SetParametersAsync)。 在高级方案中,开发人员代码可以通过不调用基类方法的任何方式解释传入参数的值。 例如,不要求将传入参数分配给类的属性。 但是,如果由于基类方法会调用其他生命周期方法并以复杂的方式触发渲染而不调用该方法,则在构造代码时必须引用 ComponentBase
引用源。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 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
,则由组件显示值。
尽管路由参数匹配不区分大小写,但 TryGetValue 只匹配路由模板中区分大小写的参数名称。 以下示例需要使用路由模板中的 /{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 核心 Blazor 同步上下文 和 ASP.NET 核心 Razor 组件处置的介绍性说明。
在服务器上预呈现其内容的 Blazor 应用调用 OnInitializedAsync两次:
- 在组件最初作为页面的一部分静态呈现时调用一次。
- 浏览器第二次呈现组件时。
为了防止 OnInitializedAsync 中的开发人员代码在预呈现时运行两次,请参阅预呈现后的有状态重新连接部分。 本节内容主要关注 Blazor Web App 和有状态的SignalR重新连接。 若要在预呈现时保持执行初始化代码期间的状态,请参阅预呈现 ASP.NET Core Razor 组件。
为了防止 OnInitializedAsync 中的开发人员代码在预呈现时运行两次,请参阅预呈现后的有状态重新连接部分。 尽管本部分中的内容重点介绍 Blazor Server 和有状态 SignalR 重新连接,但在托管 Blazor WebAssembly 解决方案 (WebAssemblyPrerendered) 中预呈现的方案涉及相似的条件和防止执行两次开发人员代码的方法。 若要在预呈现时保留初始化代码执行期间的状态,请参阅 将 ASP.NET Core Razor 组件与 MVC 或 Razor Pages 集成。
在 Blazor 应用进行预呈现时,无法执行调用 JavaScript(JS 互操作)等特定操作。 预呈现时,组件可能需要进行不同的呈现。 有关详细信息,请参阅使用 JavaScript 互操作预呈现部分。
如果在开发人员代码中提供了事件处理程序,处置时会将其解除挂接。 有关详细信息,请参阅 ASP.NET Core Razor 组件处置。
将流式渲染与静态服务器端渲染(静态 SSR)或预呈现一起使用,以改善执行 中长运行异步任务以完整渲染的组件的用户体验OnInitializedAsync。 有关更多信息,请参阅以下资源:
设置参数之后 (OnParametersSet{Async}
)
OnParametersSet 或 OnParametersSetAsync 在以下情况下调用:
在 OnInitialized 或 OnInitializedAsync 中初始化组件后。
当父组件重新呈现并提供以下内容时:
- 至少一个参数已更改时的已知或基元不可变类型。
- 复杂类型的参数。 框架无法知道复杂类型参数的值是否在内部发生了改变,因此,如果存在一个或多个复杂类型的参数,框架始终将参数集视为已更改。
有关呈现约定的详细信息,请参阅 ASP.NET Core Razor 组件呈现。
同步方法是在异步方法之前调用的。
即使参数值没有发生更改,也可以调用这些方法。 这种行为强调了开发人员需要在方法中实现其他逻辑,以在重新初始化依赖于这些参数的数据或状态之前检查参数值是否确实发生了更改。
对于以下示例组件,请导航到 URL 中的组件页面:
- 有
StartDate
收到的开始日期:/on-parameters-set/2021-03-19
- 没有开始日期,其中
StartDate
分配有当前本地时间的值:/on-parameters-set
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 核心 Blazor 同步上下文 和 ASP.NET 核心 Razor 组件处置的介绍性说明。
如果在开发人员代码中提供了事件处理程序,处置时会将其解除挂接。 有关详细信息,请参阅 ASP.NET Core Razor 组件处置。
如果一次性组件不使用 CancellationToken,OnParametersSet 和 OnParametersSetAsync 应检查组件是否已处置。 如果 OnParametersSetAsync 返回不完整 Task,组件必须确保同步完成的方法的一部分使组件处于有效的呈现状态。 有关详细信息,请参阅 ASP.NET 核心 Blazor 同步上下文 和 ASP.NET 核心 Razor 组件处置的介绍性说明。
有关路由参数和约束的详细信息,请参阅 ASP.NET Core Blazor 路由和导航。
有关在某些情况下手动实现 SetParametersAsync
以提高性能的示例,请参阅 ASP.NET 核心 Blazor 呈现性能最佳做法。
组件呈现之后 (OnAfterRender{Async}
)
OnAfterRender 和 OnAfterRenderAsync 在组件以交互方式呈现并且 UI 完成更新之后被调用(例如,元素添加到浏览器 DOM 之后)。 此时会填充元素和组件引用。 在此阶段中,可使用呈现的内容执行其他初始化步骤,例如与呈现的 DOM 元素交互的 JS 互操作调用。 同步方法是在异步方法之前调用的。
这些方法不会在预呈现或静态服务器端渲染(静态 SSR)期间在服务器上调用,因为这些进程未附加到实时浏览器 DOM,并且已在 DOM 更新之前完成。
对于 OnAfterRenderAsync,组件在任何返回 Task
的操作完成后不会自动重渲染,以避免无限渲染循环。
OnAfterRender 和 OnAfterRenderAsync 在组件完成呈现后调用。 此时会填充元素和组件引用。 在此阶段中,可使用呈现的内容执行其他初始化步骤,例如与呈现的 DOM 元素交互的 JS 互操作调用。 同步方法是在异步方法之前调用的。
这些方法在预呈现期间不会调用,因为预呈现未附加到实时浏览器 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。 在预呈现后以交互方式呈现组件时,将调用这些方法。 当应用预呈现时:
- 组件将在服务器上执行,以在 HTTP 响应中生成一些静态 HTML 标记。 在此阶段,不会调用 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。 有关事件回调的详细信息,请参阅 ASP.NET Core Blazor 事件处理。
有关组件呈现以及何时调用 StateHasChanged(包括何时通过 ComponentBase.InvokeAsync 调用它)的详细信息,请参阅 ASP.NET Core Razor 组件呈现。
处理呈现时的不完整异步操作
在呈现组件之前,在生命周期事件中执行的异步操作可能尚未完成。 执行生命周期方法时,对象可能为 null
或未完全填充数据。 提供呈现逻辑以确认对象已初始化。 对象为 null
时,呈现占位符 UI 元素(例如,加载消息)。
在以下 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
组件之前不会呈现,这需要 10 秒。 此十秒期间 UI 为空,并且没有加载消息。 预呈现后,Home
组件呈现,并显示 Slow
组件的加载消息。 再过十秒,Slow
组件最终会显示已完成的消息。
如前面的演示所示,预呈现过程中的静止会导致用户体验不佳。 若要改善用户体验,请首先实现 流呈现 以避免在预呈现时等待异步任务完成。
将 [StreamRendering]
属性 添加到 Slow
组件(在 .NET 8 中使用 [StreamRendering(true)]
):
@attribute [StreamRendering]
当 Home
组件预呈现时,Slow
组件会随加载消息快速呈现。
Home
组件不会等待 Slow
组件完成呈现的 10 秒钟。 但是,在预呈现结束时显示的已完成消息在组件最终呈现时被加载消息替换,这会导致额外的十秒延迟。 之所以发生这种情况,是因为 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();
}
}
通过将流式呈现与持久性组件状态相结合:
- 服务和数据库只需要对组件初始化进行单个调用。
- 组件会快速渲染其用户界面,并在长时间运行的任务中显示加载消息,以确保最佳用户体验。
有关更多信息,请参阅以下资源:
:::moniker-end
预呈现过程中的静止会导致用户体验不佳。 可以在面向 .NET 8 或更高版本的应用中解决延迟问题,该功能称为
处理错误
有关在生命周期方法执行期间处理错误的信息,请参阅处理 ASP.NET Core Blazor 应用中的错误。
预呈现后的有状态重新连接
在服务器上预呈现时,组件最初作为页面的一部分静态呈现。 浏览器重新建立与服务器的 SignalR 连接后,将再次呈现组件,并且该组件为交互式。 如果存在用于初始化组件的 OnInitialized{Async}
生命周期方法,则该方法执行两次:
- 在静态预呈现组件时执行一次。
- 在建立服务器连接后执行一次。
在最终呈现组件时,这可能导致 UI 中显示的数据发生明显变化。 若要避免此行为,请传递一个标识符以在预呈现期间缓存状态并在预呈现后检索状态。
以下代码演示了 WeatherForecastService
,可避免由于预呈现而更改数据显示。 等待的 Delay (await Task.Delay(...)
) 模拟先短暂延迟,然后再从 GetForecastAsync
方法返回数据。
在应用的 IMemoryCache 文件中的服务集合中添加 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 Core Razor 组件。
尽管本部分中的内容重点介绍 Blazor Server 和有状态 SignalR 重新连接,但在托管 Blazor WebAssembly 解决方案 (WebAssemblyPrerendered) 中预呈现的方案涉及相似的条件和防止执行两次开发人员代码的方法。 若要在预呈现时保留初始化代码执行期间的状态,请参阅 将 ASP.NET Core Razor 组件与 MVC 或 Razor Pages 集成。
使用 JavaScript 互操作预呈现
本部分适用于预呈现 Razor 组件的服务器端应用。 预呈现在预呈现 ASP.NET Core Razor 组件 中进行了介绍。
注意
中的Blazor Web App的内部导航不涉及从服务器请求新页面内容。 因此,内部页面请求不会启动预渲染。 如果应用采用交互式路由,请对演示预呈现行为的组件示例执行完整页面重载。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件。
本部分适用于预呈现 Blazor WebAssembly 组件的服务器端应用和托管 Razor 应用。 将 ASP.NET Core Razor 组件与 MVC 或 Razor Pages 集成中介绍了预呈现。
在预呈现期间,无法调用 JavaScript (JS)。 以下示例展示了如何以一种与预呈现兼容的方式将 JS 互操作用作组件初始化逻辑的一部分。
以下 scrollElementIntoView
函数:
- 使用
scrollIntoView
滚动到传递的元素。 - 从
top
方法返回元素的getBoundingClientRect
属性值。
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
如果 IJSRuntime.InvokeAsync 调用组件代码中的 JS 函数,则 ElementReference 仅在 OnAfterRenderAsync 中使用,而不在任何更早的生命周期方法中使用,因为呈现组件后才会有 HTML DOM 元素。
通过调用 StateHasChanged
(reference source),可使用从 JS 互操作调用获得的新状态对组件的重新呈现进行排队(有关详细信息,请参阅 ASP.NET Core Razor 组件呈现)。 不会创建无限循环,因为仅在 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 内容中。 有关详细信息,请参阅 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 连接时,只有 UI 更新会被中断。 重新建立连接后,将恢复 UI 更新。 有关线路处理程序事件和配置的详细信息,请参阅 ASP.NET Core BlazorSignalR 指南。