Skip to content

Commit a4b00e4

Browse files
authored
Merge pull request #34306 from dotnet/main
2 parents 9a29e6b + e942dec commit a4b00e4

File tree

5 files changed

+172
-16
lines changed

5 files changed

+172
-16
lines changed

aspnetcore/blazor/components/lifecycle.md

Lines changed: 154 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Component lifecycle events:
3434
1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s.
3535

3636
> [!NOTE]
37-
> 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.
37+
> 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.
3838
3939
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.
4040

@@ -61,6 +61,10 @@ The `Render` lifecycle:
6161

6262
Developer calls to [`StateHasChanged`](#state-changes-statehaschanged) result in a rerender. For more information, see <xref:blazor/components/rendering#statehaschanged>.
6363

64+
## Quiescence during prerendering
65+
66+
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.
67+
6468
## When parameters are set (`SetParametersAsync`)
6569

6670
<xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> 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
216220

217221
::: moniker range=">= aspnetcore-8.0"
218222

219-
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 <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> to fully render. For more information, see <xref:blazor/components/rendering#streaming-rendering>.
223+
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 <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> to fully render. For more information, see the following resources:
224+
225+
* [Handle incomplete asynchronous actions at render](#handle-incomplete-asynchronous-actions-at-render) (this article)
226+
* <xref:blazor/components/rendering#streaming-rendering>
220227

221228
:::moniker-end
222229

@@ -510,39 +517,173 @@ In the following example, `base.OnInitialized();` is called to ensure that the b
510517

511518
For more information on component rendering and when to call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>, including when to invoke it with <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>, see <xref:blazor/components/rendering>.
512519

513-
## Handle incomplete async actions at render
520+
## Handle incomplete asynchronous actions at render
514521

515522
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`.
516523

517-
In the following component, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> 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 <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> completes, the component is rerendered with the updated state.
524+
In the following `Slow` component, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> 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 <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> completes, the component is rerendered with the updated state, showing the "`Finished!`" message.
525+
526+
`Slow.razor`:
518527

519528
```razor
520-
<h1>Sci-Fi Movie Ratings</h1>
529+
@page "/slow"
530+
531+
<h2>Slow Component</h2>
532+
533+
@if (isLoading)
534+
{
535+
<div><em>Loading...</em></div>
536+
}
537+
else
538+
{
539+
<div>Finished!</div>
540+
}
541+
542+
@code {
543+
private bool isLoading = true;
544+
545+
protected override async Task OnInitializedAsync()
546+
{
547+
await LoadDataAsync();
548+
isLoading = false;
549+
}
550+
551+
private Task LoadDataAsync()
552+
{
553+
return Task.Delay(10000);
554+
}
555+
}
556+
```
557+
558+
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:
521559

560+
```razor
522561
@if (movies == null)
523562
{
524563
<p><em>Loading...</em></p>
525564
}
526565
else
527566
{
528-
<ul>
529-
@foreach (var movie in movies)
530-
{
531-
<li>@movie.Title &mdash; @movie.Rating</li>
532-
}
533-
</ul>
567+
@* display movies *@
534568
}
535569
536570
@code {
537571
private Movies[]? movies;
538572
539573
protected override async Task OnInitializedAsync()
540574
{
541-
movies = await GetMovieRatings(DateTime.Now);
575+
movies = await GetMovies();
542576
}
543577
}
544578
```
545579

580+
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 <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> 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:
581+
582+
```razor
583+
@page "/"
584+
585+
<PageTitle>Home</PageTitle>
586+
587+
<h1>Hello, world!</h1>
588+
589+
Welcome to your new app.
590+
591+
<SlowComponent />
592+
```
593+
594+
> [!NOTE]
595+
> Although the examples in this section discuss the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> 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.
596+
597+
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.
598+
599+
:::moniker range=">= aspnetcore-8.0"
600+
601+
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.
602+
603+
Add the [`[StreamRendering]` attribute](xref:Microsoft.AspNetCore.Components.StreamRenderingAttribute) to the `Slow` component (use `[StreamRendering(true)]` in .NET 8):
604+
605+
```razor
606+
@attribute [StreamRendering]
607+
```
608+
609+
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.
610+
611+
To address the double rendering of the loading message and the re-execution of service and database calls, persist prerendered state with <xref:Microsoft.AspNetCore.Components.PersistentComponentState> for final rendering of the component, as seen in the following updates to the `Slow` component:
612+
613+
```razor
614+
@page "/slow"
615+
@attribute [StreamRendering]
616+
@implements IDisposable
617+
@inject PersistentComponentState ApplicationState
618+
619+
<h2>Slow Component</h2>
620+
621+
@if (data is null)
622+
{
623+
<div><em>Loading...</em></div>
624+
}
625+
else
626+
{
627+
<div>@data</div>
628+
}
629+
630+
@code {
631+
private string? data;
632+
private PersistingComponentStateSubscription persistingSubscription;
633+
634+
protected override async Task OnInitializedAsync()
635+
{
636+
persistingSubscription =
637+
ApplicationState.RegisterOnPersisting(PersistData);
638+
639+
if (!ApplicationState.TryTakeFromJson<string>("data", out var restored))
640+
{
641+
data = await LoadDataAsync();
642+
}
643+
else
644+
{
645+
data = restored!;
646+
}
647+
}
648+
649+
private Task PersistData()
650+
{
651+
ApplicationState.PersistAsJson("data", data);
652+
653+
return Task.CompletedTask;
654+
}
655+
656+
private async Task<string> LoadDataAsync()
657+
{
658+
await Task.Delay(10000);
659+
return "Finished!";
660+
}
661+
662+
void IDisposable.Dispose()
663+
{
664+
persistingSubscription.Dispose();
665+
}
666+
}
667+
```
668+
669+
By combining streaming rendering with persistent component state:
670+
671+
* Services and databases only require a single call for component initialization.
672+
* Components render their UIs quickly with loading messages during long-running tasks for the best user experience.
673+
674+
For more information, see the following resources:
675+
676+
* <xref:blazor/components/rendering#streaming-rendering>
677+
* <xref:blazor/components/prerender#persist-prerendered-state>.
678+
679+
:::moniker-end
680+
681+
:::moniker range="< aspnetcore-8.0"
682+
683+
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.
684+
685+
:::moniker-end
686+
546687
## Handle errors
547688

548689
For information on handling errors during lifecycle method execution, see <xref:blazor/fundamentals/handle-errors>.
@@ -972,7 +1113,7 @@ To implement a cancelable background work pattern in a component:
9721113

9731114
In the following example:
9741115

975-
* `await Task.Delay(5000, cts.Token);` represents long-running asynchronous background work.
1116+
* `await Task.Delay(10000, cts.Token);` represents long-running asynchronous background work.
9761117
* `BackgroundResourceMethod` represents a long-running background method that shouldn't start if the `Resource` is disposed before the method is called.
9771118

9781119
`BackgroundWork.razor`:

aspnetcore/blazor/components/prerender.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Prerendering guidance is organized in the Blazor documentation by subject matter
151151
* [After component render (`OnAfterRender{Async}`)](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync)
152152
* [Stateful reconnection after prerendering](xref:blazor/components/lifecycle#stateful-reconnection-after-prerendering)
153153
* [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.
154+
* [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.
154155
* [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).
155156
* [Prerendering when integrating components into Razor Pages and MVC apps](xref:blazor/components/integration)
156157

aspnetcore/blazor/components/rendering.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ For example, consider a component that makes a long-running database query or we
5151

5252
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.
5353

54-
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).
54+
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).
5555

5656
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 <xref:System.Threading.Tasks.Task.Delay%2A?displayProperty=nameWithType> 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.
5757

5858
`Weather.razor`:
5959

6060
```razor
6161
@page "/weather"
62-
@attribute [StreamRendering(true)]
62+
@attribute [StreamRendering]
6363
6464
...
6565

aspnetcore/blazor/host-and-deploy/webassembly.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ In the `wwwroot/index.html` file, set `autostart` to `false` on Blazor's `<scrip
116116
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
117117
```
118118

119-
After Blazor's `<script>` tag and before the closing `</body>` tag, add the following JavaScript code `<script>` block.
119+
After Blazor's `<script>` tag and before the closing `</body>` tag, add the following JavaScript code `<script>` block. The following function calls `fetch` with [`cache: 'no-cache'`](https://developer.mozilla.org/docs/Web/API/Request/cache#value) to keep the browser's cache updated.
120120

121121
:::moniker range=">= aspnetcore-8.0"
122122

aspnetcore/migration/80-to-90/includes/blazor.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,17 @@ In the client project (`.Client`):
3535
```
3636

3737
For more information, see <xref:aspnetcore-9#simplified-authentication-state-serialization-for-blazor-web-apps>.
38+
39+
### Streaming rendering attribute no longer requires the `true` parameter
40+
41+
In .NET 8, [streaming rendering](xref:blazor/components/rendering?view=aspnetcore-8.0&preserve-view=true#streaming-rendering) required you to pass `true` for the `enabled` parameter:
42+
43+
```razor
44+
@attribute [StreamRendering(true)]
45+
```
46+
47+
In .NET 9 or later, `true` can optionally be removed, as `true` is now the default for the `enabled` parameter:
48+
49+
```razor
50+
@attribute [StreamRendering]
51+
```

0 commit comments

Comments
 (0)