From 1964750605fd5405cee6483d60f689c205a8b937 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:11:06 -0500 Subject: [PATCH 1/2] Blazor preserve state during prerendering --- .../prerendering-and-integration.md | 137 +++++++++++++++--- .../built-in/component-tag-helper.md | 3 +- .../mvc/views/tag-helpers/built-in/index.md | 1 - .../built-in/preserve-component-state.md | 103 +++++++++++++ aspnetcore/toc.yml | 2 + 5 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md diff --git a/aspnetcore/blazor/components/prerendering-and-integration.md b/aspnetcore/blazor/components/prerendering-and-integration.md index 67182a576d09..ea5206a797df 100644 --- a/aspnetcore/blazor/components/prerendering-and-integration.md +++ b/aspnetcore/blazor/components/prerendering-and-integration.md @@ -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 () 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 @@ -676,6 +655,122 @@ The `_ViewImports.cshtml` file is located in the `Pages` folder of a Razor Pages For more information, see . +::: zone-end + +::: moniker range=">= aspnetcore-6.0" + +## Preserve prerendered state + +Without preserving prerendered state, any state that was used during prerendering is lost and must be recreated when the app is fully loaded. If any state is setup asynchronously, then the UI may flicker as the the prerendered UI is replaced with temporary placeholders and then fully rendered again. + +To solve this problem, Blazor supports persisting state into the prerendered page using the [Preserve Component State Tag Helper](xref:mvc/views/tag-helpers/builtin-th/preserve-component-state-tag-helper) (``). + +::: zone pivot="webassembly" + +`Pages/_Host.cshtml`: + +```cshtml + + + + ... + + + +``` + +::: zone-end + +::: zone pivot="server" + +`Pages/_Host.cshtml`: + +```cshtml + + + + ... + + + +``` + +::: zone-end + +In the app, you 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. + +The following example below shows how the weather forecasts in the `FetchData` component from an app based on the Blazor project template can be persisted during prerendering and then retrieved to initialize the component. The Persist Component State Tag Helper persists the component state after all component invocations. + +`FetchData.razor`: + +```razor +@page "/fetchdata" +@implements IDisposable +@inject ComponentApplicationState ApplicationState + +... + +@code { + protected override async Task OnInitializedAsync() + { + ApplicationState.OnPersisting += PersistForecasts; + + if (!ApplicationState + .TryRedeemFromJson("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. + +::: 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 () 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) diff --git a/aspnetcore/mvc/views/tag-helpers/built-in/component-tag-helper.md b/aspnetcore/mvc/views/tag-helpers/built-in/component-tag-helper.md index 18132aff931b..13f23a4e8b45 100644 --- a/aspnetcore/mvc/views/tag-helpers/built-in/component-tag-helper.md +++ b/aspnetcore/mvc/views/tag-helpers/built-in/component-tag-helper.md @@ -3,6 +3,7 @@ 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] @@ -10,8 +11,6 @@ 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" diff --git a/aspnetcore/mvc/views/tag-helpers/built-in/index.md b/aspnetcore/mvc/views/tag-helpers/built-in/index.md index 253602df4cb6..5b08fa82d398 100644 --- a/aspnetcore/mvc/views/tag-helpers/built-in/index.md +++ b/aspnetcore/mvc/views/tag-helpers/built-in/index.md @@ -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) diff --git a/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md b/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md new file mode 100644 index 000000000000..1aa0f0898b17 --- /dev/null +++ b/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md @@ -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/14/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 into prerendered components, use the Preserve Component State Tag Helper (). Add the `` tag inside the closing `` tag of the `_Host` page in an app that prerenders components: + +::: zone pivot="webassembly" + +`Pages/_Host.cshtml`: + +```cshtml + + + + ... + + + +``` + +::: zone-end + +::: zone pivot="server" + +`Pages/_Host.cshtml`: + +```cshtml + + + + ... + + + +``` + +::: 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 + .TryRedeemFromJson<{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 . + +## Additional resources + +* +* +* diff --git a/aspnetcore/toc.yml b/aspnetcore/toc.yml index 0c85284b5e92..7dcaca5125b9 100644 --- a/aspnetcore/toc.yml +++ b/aspnetcore/toc.yml @@ -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 From 083585b1f8710cabb8a3c28bdcb008b38ffea860 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 16 Jul 2021 04:03:44 -0500 Subject: [PATCH 2/2] Updates --- .../components/prerendering-and-integration.md | 14 +++++++------- .../built-in/preserve-component-state.md | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aspnetcore/blazor/components/prerendering-and-integration.md b/aspnetcore/blazor/components/prerendering-and-integration.md index ea5206a797df..2c9ac264b122 100644 --- a/aspnetcore/blazor/components/prerendering-and-integration.md +++ b/aspnetcore/blazor/components/prerendering-and-integration.md @@ -661,9 +661,9 @@ For more information, see . ## Preserve prerendered state -Without preserving prerendered state, any state that was used during prerendering is lost and must be recreated when the app is fully loaded. If any state is setup asynchronously, then the UI may flicker as the the prerendered UI is replaced with temporary placeholders and then fully rendered again. +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 this problem, Blazor supports persisting state into the prerendered page using the [Preserve Component State Tag Helper](xref:mvc/views/tag-helpers/builtin-th/preserve-component-state-tag-helper) (``). +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) (``). Add the `` tag inside the closing `` tag of `_Host.cshtml`. ::: zone pivot="webassembly" @@ -697,11 +697,11 @@ To solve this problem, Blazor supports persisting state into the prerendered pag ::: zone-end -In the app, you 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 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 below shows how the weather forecasts in the `FetchData` component from an app based on the Blazor project template can be persisted during prerendering and then retrieved to initialize the component. The Persist Component State Tag Helper persists the component state after all component invocations. +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. -`FetchData.razor`: +`Pages/FetchData.razor`: ```razor @page "/fetchdata" @@ -716,7 +716,7 @@ The following example below shows how the weather forecasts in the `FetchData` c ApplicationState.OnPersisting += PersistForecasts; if (!ApplicationState - .TryRedeemFromJson("fetchdata", out var forecasts)) + .TryTakeAsJson("fetchdata", out var forecasts)) { forecasts = await ForecastService.GetForecastAsync(DateTime.Now); } @@ -736,7 +736,7 @@ The following example below shows how the weather forecasts in the `FetchData` c } ``` -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. +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 diff --git a/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md b/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md index 1aa0f0898b17..c1c41556a891 100644 --- a/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md +++ b/aspnetcore/mvc/views/tag-helpers/built-in/preserve-component-state.md @@ -5,7 +5,7 @@ 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/14/2021 +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 @@ -21,7 +21,7 @@ Follow the guidance in the *Configuration* section for either: ## Preserve Component State Tag Helper -To preserve state into prerendered components, use the Preserve Component State Tag Helper (). Add the `` tag inside the closing `` tag of the `_Host` page in an app that prerenders components: +To preserve state for prerendered components, use the Preserve Component State Tag Helper (``). Add the `` tag inside the closing `` tag of the `_Host` page in an app that prerenders components: ::: zone pivot="webassembly" @@ -74,7 +74,7 @@ In the following example: ApplicationState.OnPersisting += PersistData; if (!ApplicationState - .TryRedeemFromJson<{TYPE}>("{TOKEN}", out var data)) + .TryTakeAsJson<{TYPE}>("{TOKEN}", out var data)) { data = ...; }