Skip to content

Blazor preserve state during prerendering #22732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 116 additions & 21 deletions aspnetcore/blazor/components/prerendering-and-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,27 +258,6 @@ Additional work might be required depending on the static resources that compone
>
> This is normal behavior because prerendering and integrating a Blazor WebAssembly app with routable Razor components is incompatible with the use of CSS selectors.

## Additional Blazor WebAssembly resources

* [Hosted Blazor WebAssembly logging](xref:blazor/fundamentals/logging#hosted-blazor-webassembly-logging)
* [State management: Handle prerendering](xref:blazor/state-management#handle-prerendering)
* [Prerendering support with assembly lazy loading](xref:blazor/webassembly-lazy-load-assemblies#lazy-load-assemblies-in-a-hosted-blazor-webassembly-solution)
* Razor component lifecycle subjects that pertain to prerendering
* [Component initialization (`OnInitialized{Async}`)](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)
* [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): Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. A *new state preservation feature* is planned for the ASP.NET Core 6.0 release that will improve the management of initialization code execution during prerendering.
* [Detect when the app is prerendering](xref:blazor/components/lifecycle#detect-when-the-app-is-prerendering)
* Authentication and authorization subjects that pertain to prerendering
* [General aspects](xref:blazor/security/index#aspnet-core-blazor-authentication-and-authorization)
* [Support prerendering with authentication](xref:blazor/security/webassembly/additional-scenarios#support-prerendering-with-authentication)
* [Host and deploy: Blazor WebAssembly](xref:blazor/host-and-deploy/webassembly)

::: moniker-end

::: moniker range="< aspnetcore-5.0"

Integrating Razor components into Razor Pages and MVC apps in a hosted Blazor WebAssembly solution is supported in ASP.NET Core in .NET 5 or later. Select a .NET 5 or later version of this article.

::: moniker-end

::: zone-end
Expand Down Expand Up @@ -676,6 +655,122 @@ The `_ViewImports.cshtml` file is located in the `Pages` folder of a Razor Pages

For more information, see <xref:blazor/components/index#namespaces>.

::: zone-end

::: moniker range=">= aspnetcore-6.0"

## Preserve prerendered state

Without preserving prerendered state, any state that used during prerendering is lost and must be recreated when the app is fully loaded. If any state is setup asynchronously, the UI may flicker as the the prerendered UI is replaced with temporary placeholders and then fully rendered again.

To solve these problems, Blazor supports persisting state in a prerendered page using the [Preserve Component State Tag Helper](xref:mvc/views/tag-helpers/builtin-th/preserve-component-state-tag-helper) (`<preserve-component-state />`). Add the `<preserve-component-state />` tag inside the closing `</body>` tag of `_Host.cshtml`.

::: zone pivot="webassembly"

`Pages/_Host.cshtml`:

```cshtml
<body>
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

...

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

::: zone-end

::: zone pivot="server"

`Pages/_Host.cshtml`:

```cshtml
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />

...

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

::: zone-end

In the app, decide what state to persist using the `ComponentApplicationState` service. The `ComponentApplicationState.OnPersisting` event is fired just before the state is persisted into the prerendered page, which allows a component to retrieve the state when initializing the component.

The following example shows how the weather forecast in the `FetchData` component from an app based on the Blazor project template is persisted during prerendering and then retrieved to initialize the component. The Persist Component State Tag Helper persists the component state after all component invocations.

`Pages/FetchData.razor`:

```razor
@page "/fetchdata"
@implements IDisposable
@inject ComponentApplicationState ApplicationState

...

@code {
protected override async Task OnInitializedAsync()
{
ApplicationState.OnPersisting += PersistForecasts;

if (!ApplicationState
.TryTakeAsJson<WeatherForecast[]>("fetchdata", out var forecasts))
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}

private Task PersistForecasts()
{
ApplicationState.PersistAsJson("fetchdata", forecasts);

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
ApplicationState.OnPersisting -= PersistForecasts;
}
}
```

By initializing components with the same state used during prerendering, any expensive initialization steps are only executed once. The rendered UI also matches the prerendered UI, so no flicker occurs in the browser.

::: moniker-end

::: zone pivot="webassembly"

::: moniker range=">= aspnetcore-5.0"

## Additional Blazor WebAssembly resources

* [Hosted Blazor WebAssembly logging](xref:blazor/fundamentals/logging#hosted-blazor-webassembly-logging)
* [State management: Handle prerendering](xref:blazor/state-management#handle-prerendering)
* [Prerendering support with assembly lazy loading](xref:blazor/webassembly-lazy-load-assemblies#lazy-load-assemblies-in-a-hosted-blazor-webassembly-solution)
* Razor component lifecycle subjects that pertain to prerendering
* [Component initialization (`OnInitialized{Async}`)](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)
* [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): Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. A *new state preservation feature* is planned for the ASP.NET Core 6.0 release that will improve the management of initialization code execution during prerendering.
* [Detect when the app is prerendering](xref:blazor/components/lifecycle#detect-when-the-app-is-prerendering)
* Authentication and authorization subjects that pertain to prerendering
* [General aspects](xref:blazor/security/index#aspnet-core-blazor-authentication-and-authorization)
* [Support prerendering with authentication](xref:blazor/security/webassembly/additional-scenarios#support-prerendering-with-authentication)
* [Host and deploy: Blazor WebAssembly](xref:blazor/host-and-deploy/webassembly)

::: moniker-end

::: moniker range="< aspnetcore-5.0"

Integrating Razor components into Razor Pages and MVC apps in a hosted Blazor WebAssembly solution is supported in ASP.NET Core in .NET 5 or later. Select a .NET 5 or later version of this article.

::: moniker-end

::: zone-end

::: zone pivot="server"

## Additional Blazor Server resources

* [State management: Handle prerendering](xref:blazor/state-management#handle-prerendering)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ title: Component Tag Helper in ASP.NET Core
author: guardrex
ms.author: riande
description: Learn how to use the ASP.NET Core Component Tag Helper to render Razor components in pages and views.
monikerRange: '>= aspnetcore-3.1'
ms.custom: mvc
ms.date: 10/29/2020
no-loc: [Home, Privacy, Kestrel, appsettings.json, "ASP.NET Core Identity", cookie, Cookie, Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR]
uid: mvc/views/tag-helpers/builtin-th/component-tag-helper
---
# Component Tag Helper in ASP.NET Core

By [Daniel Roth](https://github.com/danroth27) and [Luke Latham](https://github.com/guardrex)

## Prerequisites

::: moniker range=">= aspnetcore-5.0"
Expand Down
1 change: 0 additions & 1 deletion aspnetcore/mvc/views/tag-helpers/built-in/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ ms.date: 10/10/2018
no-loc: [Home, Privacy, Kestrel, appsettings.json, "ASP.NET Core Identity", cookie, Cookie, Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR]
uid: mvc/views/tag-helpers/builtin-th/Index
---

# ASP.NET Core built-in Tag Helpers

By [Peter Kellner](https://peterkellner.net)
Expand Down
103 changes: 103 additions & 0 deletions aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Preserve Component State Tag Helper in ASP.NET Core
author: guardrex
ms.author: riande
description: Learn how to use the ASP.NET Core Preserve Component State Tag Helper to preserve state when prerendering components.
monikerRange: '>= aspnetcore-6.0'
ms.custom: mvc
ms.date: 07/16/2021
no-loc: [Home, Privacy, Kestrel, appsettings.json, "ASP.NET Core Identity", cookie, Cookie, Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR]
uid: mvc/views/tag-helpers/builtin-th/preserve-component-state-tag-helper
zone_pivot_groups: blazor-hosting-models
---
# Preserve Component State Tag Helper in ASP.NET Core

## Prerequisites

Follow the guidance in the *Configuration* section for either:

* [Blazor WebAssembly](xref:blazor/components/prerendering-and-integration?pivots=webassembly)
* [Blazor Server](xref:blazor/components/prerendering-and-integration?pivots=server)

## Preserve Component State Tag Helper

To preserve state for prerendered components, use the Preserve Component State Tag Helper (`<persist-component-state />`). Add the `<preserve-component-state />` tag inside the closing `</body>` tag of the `_Host` page in an app that prerenders components:

::: zone pivot="webassembly"

`Pages/_Host.cshtml`:

```cshtml
<body>
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

...

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

::: zone-end

::: zone pivot="server"

`Pages/_Host.cshtml`:

```cshtml
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />

...

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

::: zone-end

Decide what state to persist using the `ComponentApplicationState` service. The `ComponentApplicationState.OnPersisting` event is fired just before the state is persisted into the prerendered page, which allows you to retrieve any persisted state when initializing a component.

In the following example:

* The `{TYPE}` placeholder represents the type of data to persist (for example, `WeatherForecast[]`).
* The `{TOKEN}` placeholder is a state identifier string (for example, `fetchdata`).

```razor
@implements IDisposable
@inject ComponentApplicationState ApplicationState

...

@code {
protected override async Task OnInitializedAsync()
{
ApplicationState.OnPersisting += PersistData;

if (!ApplicationState
.TryTakeAsJson<{TYPE}>("{TOKEN}", out var data))
{
data = ...;
}
}

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

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
ApplicationState.OnPersisting -= PersistData;
}
}
```

For more information and a complete example, see <xref:blazor/components/prerendering-and-integration#preserve-prerendered-state>.

## Additional resources

* <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper>
* <xref:mvc/views/tag-helpers/intro>
* <xref:blazor/components/index>
2 changes: 2 additions & 0 deletions aspnetcore/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@
uid: mvc/views/tag-helpers/builtin-th/link-tag-helper
- name: Partial
uid: mvc/views/tag-helpers/builtin-th/partial-tag-helper
- name: Preserve Component State
uid: mvc/views/tag-helpers/builtin-th/preserve-component-state-tag-helper
- name: Script
uid: mvc/views/tag-helpers/builtin-th/script-tag-helper
- name: Select
Expand Down