diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 2125607c95bc..8bc35d425b2c 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -34,7 +34,7 @@ Component lifecycle events: 1. Render for all synchronous work and complete s. > [!NOTE] -> Asynchronous actions performed in lifecycle events might not complete before a component is rendered. For more information, see the [Handle incomplete async actions at render](#handle-incomplete-async-actions-at-render) section later in this article. +> Asynchronous actions performed in lifecycle events might delay component rendering or displaying data. For more information, see the [Handle incomplete asynchronous actions at render](#handle-incomplete-asynchronous-actions-at-render) section later in this article. A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running. @@ -61,6 +61,10 @@ The `Render` lifecycle: Developer calls to [`StateHasChanged`](#state-changes-statehaschanged) result in a rerender. For more information, see . +## Quiescence during prerendering + +In server-side Blazor apps, prerendering waits for *quiescence*, which means that a component doesn't render until all of the components in the render tree have finished rendering. Quiescence can lead to noticeable delays in rendering when a component performs long-running tasks during initialization and other lifecycle methods, leading to a poor user experience. For more information, see the [Handle incomplete asynchronous actions at render](#handle-incomplete-asynchronous-actions-at-render) section later in this article. + ## When parameters are set (`SetParametersAsync`) sets parameters supplied by the component's parent in the render tree or from route parameters. @@ -216,7 +220,10 @@ If event handlers are provided in developer code, unhook them on disposal. For m ::: moniker range=">= aspnetcore-8.0" -Use *streaming rendering* with static server-side rendering (static SSR) or prerendering to improve the user experience for components that perform long-running asynchronous tasks in to fully render. For more information, see . +Use *streaming rendering* with static server-side rendering (static SSR) or prerendering to improve the user experience for components that perform long-running asynchronous tasks in to fully render. For more information, see the following resources: + +* [Handle incomplete asynchronous actions at render](#handle-incomplete-asynchronous-actions-at-render) (this article) +* :::moniker-end @@ -510,27 +517,54 @@ In the following example, `base.OnInitialized();` is called to ensure that the b For more information on component rendering and when to call , including when to invoke it with , see . -## Handle incomplete async actions at render +## Handle incomplete asynchronous actions at render Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be `null` or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are `null`. -In the following component, is overridden to asynchronously provide movie rating data (`movies`). When `movies` is `null`, a loading message is displayed to the user. After the `Task` returned by completes, the component is rerendered with the updated state. +In the following `Slow` component, is overridden to asynchronously execute a long-running task. While `isLoading` is `true`, a loading message is displayed to the user. After the `Task` returned by completes, the component is rerendered with the updated state, showing the "`Finished!`" message. + +`Slow.razor`: ```razor -

Sci-Fi Movie Ratings

+@page "/slow" + +

Slow Component

+ +@if (isLoading) +{ +
Loading...
+} +else +{ +
Finished!
+} + +@code { + private bool isLoading = true; + + protected override async Task OnInitializedAsync() + { + await LoadDataAsync(); + isLoading = false; + } + + private Task LoadDataAsync() + { + return Task.Delay(10000); + } +} +``` + +The preceding component uses an `isLoading` variable to display the loading message. A similar approach is used for a component that loads data into a collection and checks if the collection is `null` to present the loading message. The following example checks the `movies` collection for `null` to either display the loading message or display the collection of movies: +```razor @if (movies == null) {

Loading...

} else { -
    - @foreach (var movie in movies) - { -
  • @movie.Title — @movie.Rating
  • - } -
+ @* display movies *@ } @code { @@ -538,11 +572,118 @@ else protected override async Task OnInitializedAsync() { - movies = await GetMovieRatings(DateTime.Now); + movies = await GetMovies(); } } ``` +Prerendering waits for *quiescence*, which means that a component doesn't render until all of the components in the render tree have finished rendering. This means that a loading message doesn't display while a child component's method is executing a long-running task during prerendering. To demonstrate this behavior, place the preceding `Slow` component into a test app's `Home` component: + +```razor +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. + + +``` + +> [!NOTE] +> Although the examples in this section discuss the lifecycle method, other lifecycle methods that execute during prerendering may delay final rendering of a component. Only [`OnAfterRender{Async}`](#after-component-render-onafterrenderasync) isn't executed during prerendering and is immune to delays due to quiescence. + +During prerendering, the `Home` component doesn't render until the `Slow` component is rendered, which takes ten seconds. The UI is blank during this ten-second period, and there's no loading message. After prerendering, the `Home` component renders, and the `Slow` component's loading message is displayed. After ten more seconds, the `Slow` component finally displays the finished message. + +:::moniker range=">= aspnetcore-8.0" + +As the preceding demonstration illustrates, quiescence during prerendering results in a poor user experience. To improve the user experience, begin by implementing [streaming rendering](xref:blazor/components/rendering#streaming-rendering) to avoid waiting for the asynchronous task to complete while prerendering. + +Add the [`[StreamRendering]` attribute](xref:Microsoft.AspNetCore.Components.StreamRenderingAttribute) to the `Slow` component (use `[StreamRendering(true)]` in .NET 8): + +```razor +@attribute [StreamRendering] +``` + +When the `Home` component is prerendering, the `Slow` component is quickly rendered with its loading message. The `Home` component doesn't wait for ten seconds for the `Slow` component to finish rendering. However, the finished message displayed at the end of prerendering is replaced by the loading message while the component finally renders, which is another ten-second delay. This occurs because the `Slow` component is rendering twice, along with `LoadDataAsync` executing twice. When a component is accessing resources, such as services and databases, double execution of service calls and database queries creates undesirable load on the app's resources. + +To address the double rendering of the loading message and the re-execution of service and database calls, persist prerendered state with for final rendering of the component, as seen in the following updates to the `Slow` component: + +```razor +@page "/slow" +@attribute [StreamRendering] +@implements IDisposable +@inject PersistentComponentState ApplicationState + +

Slow Component

+ +@if (data is null) +{ +
Loading...
+} +else +{ +
@data
+} + +@code { + private string? data; + private PersistingComponentStateSubscription persistingSubscription; + + protected override async Task OnInitializedAsync() + { + persistingSubscription = + ApplicationState.RegisterOnPersisting(PersistData); + + if (!ApplicationState.TryTakeFromJson("data", out var restored)) + { + data = await LoadDataAsync(); + } + else + { + data = restored!; + } + } + + private Task PersistData() + { + ApplicationState.PersistAsJson("data", data); + + return Task.CompletedTask; + } + + private async Task LoadDataAsync() + { + await Task.Delay(10000); + return "Finished!"; + } + + void IDisposable.Dispose() + { + persistingSubscription.Dispose(); + } +} +``` + +By combining streaming rendering with persistent component state: + +* Services and databases only require a single call for component initialization. +* Components render their UIs quickly with loading messages during long-running tasks for the best user experience. + +For more information, see the following resources: + +* +* . + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +Quiescence during prerendering results in a poor user experience. The delay can be addressed in apps that target .NET 8 or later with a feature called *streaming rendering*, usually combined with [persisting component state during prerendering](xref:blazor/components/integration#persist-prerendered-state) to avoid waiting for the asynchronous task to complete. In versions of .NET earlier than 8.0, executing a [long-running background task](#cancelable-background-work) that loads the data after final rendering can address a long rendering delay due to quiescence. + +:::moniker-end + ## Handle errors For information on handling errors during lifecycle method execution, see . @@ -972,7 +1113,7 @@ To implement a cancelable background work pattern in a component: In the following example: -* `await Task.Delay(5000, cts.Token);` represents long-running asynchronous background work. +* `await Task.Delay(10000, cts.Token);` represents long-running asynchronous background work. * `BackgroundResourceMethod` represents a long-running background method that shouldn't start if the `Resource` is disposed before the method is called. `BackgroundWork.razor`: diff --git a/aspnetcore/blazor/components/prerender.md b/aspnetcore/blazor/components/prerender.md index 241e1cbe38cd..7cf923522ba1 100644 --- a/aspnetcore/blazor/components/prerender.md +++ b/aspnetcore/blazor/components/prerender.md @@ -151,6 +151,7 @@ Prerendering guidance is organized in the Blazor documentation by subject matter * [After component render (`OnAfterRender{Async}`)](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync) * [Stateful reconnection after prerendering](xref:blazor/components/lifecycle#stateful-reconnection-after-prerendering) * [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop): This section also appears in the two JS interop articles on calling JavaScript from .NET and calling .NET from JavaScript. + * [Handle incomplete asynchronous actions at render](xref:blazor/components/lifecycle#handle-incomplete-asynchronous-actions-at-render): Guidance for delayed rendering due to long-running lifecycle tasks during prerendering on the server. * [QuickGrid component sample app](xref:blazor/components/quickgrid#sample-app): The [**QuickGrid for Blazor** sample app](https://aspnet.github.io/quickgridsamples/) is hosted on GitHub Pages. The site loads fast thanks to static prerendering using the community-maintained [`BlazorWasmPrerendering.Build` GitHub project](https://github.com/jsakamoto/BlazorWasmPreRendering.Build). * [Prerendering when integrating components into Razor Pages and MVC apps](xref:blazor/components/integration) diff --git a/aspnetcore/blazor/components/rendering.md b/aspnetcore/blazor/components/rendering.md index e71659691cc9..7aaf7080223b 100644 --- a/aspnetcore/blazor/components/rendering.md +++ b/aspnetcore/blazor/components/rendering.md @@ -51,7 +51,7 @@ For example, consider a component that makes a long-running database query or we Streaming rendering requires the server to avoid buffering the output. The response data must flow to the client as the data is generated. For hosts that enforce buffering, streaming rendering degrades gracefully, and the page loads without streaming rendering. -To stream content updates when using static server-side rendering (static SSR) or prerendering, apply the `[StreamRendering(true)]` attribute to the component. Streaming rendering must be explicitly enabled because streamed updates may cause content on the page to shift. Components without the attribute automatically adopt streaming rendering if the parent component uses the feature. Pass `false` to the attribute in a child component to disable the feature at that point and further down the component subtree. The attribute is functional when applied to components supplied by a [Razor class library](xref:blazor/components/class-libraries). +To stream content updates when using static server-side rendering (static SSR) or prerendering, apply the [`[StreamRendering]` attribute](xref:Microsoft.AspNetCore.Components.StreamRenderingAttribute) in .NET 9 or later (use `[StreamRendering(true)]` in .NET 8) to the component. Streaming rendering must be explicitly enabled because streamed updates may cause content on the page to shift. Components without the attribute automatically adopt streaming rendering if the parent component uses the feature. Pass `false` to the attribute in a child component to disable the feature at that point and further down the component subtree. The attribute is functional when applied to components supplied by a [Razor class library](xref:blazor/components/class-libraries). The following example is based on the `Weather` component in an app created from the [Blazor Web App project template](xref:blazor/project-structure#blazor-web-app). The call to simulates retrieving weather data asynchronously. The component initially renders placeholder content ("`Loading...`") without waiting for the asynchronous delay to complete. When the asynchronous delay completes and the weather data content is generated, the content is streamed to the response and patched into the weather forecast table. @@ -59,7 +59,7 @@ The following example is based on the `Weather` component in an app created from ```razor @page "/weather" -@attribute [StreamRendering(true)] +@attribute [StreamRendering] ... diff --git a/aspnetcore/blazor/host-and-deploy/webassembly.md b/aspnetcore/blazor/host-and-deploy/webassembly.md index cac1018d8571..c411e6e0a1d7 100644 --- a/aspnetcore/blazor/host-and-deploy/webassembly.md +++ b/aspnetcore/blazor/host-and-deploy/webassembly.md @@ -116,7 +116,7 @@ In the `wwwroot/index.html` file, set `autostart` to `false` on Blazor's ` ``` -After Blazor's `