Skip to content

Commit 4477d29

Browse files
authored
Improve state persistence service example (#36187)
1 parent f4bcbc0 commit 4477d29

File tree

2 files changed

+66
-14
lines changed

2 files changed

+66
-14
lines changed

aspnetcore/blazor/state-management/prerendered-state-persistence.md

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ The first logged count occurs during prerendering. The count is set again after
3232

3333
To retain the initial value of the counter during prerendering, Blazor supports persisting state in a prerendered page using the <xref:Microsoft.AspNetCore.Components.PersistentComponentState> service (and for components embedded into pages or views of Razor Pages or MVC apps, the [Persist Component State Tag Helper](xref:mvc/views/tag-helpers/builtin-th/persist-component-state-tag-helper)).
3434

35+
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.
36+
37+
The persisted prerendered state is transferred to the client, where it's used to restore the component state. During client-side rendering (CSR, `InteractiveWebAssembly`), the data is exposed to the browser and must not contain sensitive, private information. During interactive server-side rendering (interactive SSR, `InteractiveServer`), [ASP.NET Core Data Protection](xref:security/data-protection/introduction) ensures that the data is transferred securely. The `InteractiveAuto` render mode combines WebAssembly and Server interactivity, so it's necessary to consider data exposure to the browser, as in the CSR case.
38+
3539
:::moniker range=">= aspnetcore-10.0"
3640

3741
<!-- UPDATE 10.0 - API cross-links -->
@@ -136,6 +140,8 @@ In the following example that serializes state for multiple components of the sa
136140
}
137141
```
138142

143+
## Serialize state for services
144+
139145
In the following example that serializes state for a dependency injection service:
140146

141147
* Properties annotated with the `[PersistentState]` attribute are serialized during prerendering and deserialized when the app becomes interactive.
@@ -148,12 +154,19 @@ In the following example that serializes state for a dependency injection servic
148154
> [!NOTE]
149155
> Only persisting scoped services is supported.
150156
151-
<!-- UPDATE 10.0 - Flesh out with a fully-working example. -->
157+
Serialized properties are identified from the actual service instance:
152158

153-
`CounterService.cs`:
159+
* This approach allows marking an abstraction as a persistent service.
160+
* Enables actual implementations to be internal or different types.
161+
* Supports shared code in different assemblies.
162+
* Results in each instance exposing the same properties.
163+
164+
The following counter service, `CounterTracker`, marks its current count property, `CurrentCount` with the `[PersistentState]` attribute. The property is serialized during prerendering and deserialized when the app becomes interactive wherever the service is injected.
165+
166+
`CounterTracker.cs`:
154167

155168
```csharp
156-
public class CounterService
169+
public class CounterTracker
157170
{
158171
[PersistentState]
159172
public int CurrentCount { get; set; }
@@ -165,19 +178,60 @@ public class CounterService
165178
}
166179
```
167180

168-
In `Program.cs`:
181+
In the `Program` file, register the scoped service and register the service for persistence with `RegisterPersistentService`. In the following example, the `CounterTracker` service is available for both the Interactive Server and Interactive WebAssembly render modes if a component renders in either of those modes because it's registered with `RenderMode.InteractiveAuto`.
182+
183+
If the `Program` file doesn't already use the <xref:Microsoft.AspNetCore.Components.Web?displayProperty=fullName> namespace, add the following `using` statement to the top of the file:
169184

170185
```csharp
186+
using Microsoft.AspNetCore.Components.Web;
187+
```
188+
189+
Where services are registered in the `Program` file:
190+
191+
```csharp
192+
builder.Services.AddScoped<CounterTracker>();
193+
171194
builder.Services.AddRazorComponents()
172-
.RegisterPersistentService<CounterService>(RenderMode.InteractiveAuto);
195+
.RegisterPersistentService<CounterTracker>(RenderMode.InteractiveAuto);
173196
```
174197

175-
Serialized properties are identified from the actual service instance:
198+
Inject the `CounterTracker` service into a component and use it to increment a counter. For demonstration purposes in the following example, the value of the service's `CurrentCount` property is set to 10 only during prerendering.
176199

177-
* This approach allows marking an abstraction as a persistent service.
178-
* Enables actual implementations to be internal or different types.
179-
* Supports shared code in different assemblies.
180-
* Results in each instance exposing the same properties.
200+
`Pages/Counter.razor`:
201+
202+
```razor
203+
@page "/counter"
204+
@inject CounterTracker CounterTracker
205+
206+
<PageTitle>Counter</PageTitle>
207+
208+
<h1>Counter</h1>
209+
210+
<p>Rendering: @RendererInfo.Name</p>
211+
212+
<p role="status">Current count: @CounterTracker.CurrentCount</p>
213+
214+
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
215+
216+
@code {
217+
protected override void OnInitialized()
218+
{
219+
if (!RendererInfo.IsInteractive)
220+
{
221+
CounterTracker.CurrentCount = 10;
222+
}
223+
}
224+
225+
private void IncrementCount()
226+
{
227+
CounterTracker.IncrementCount();
228+
}
229+
}
230+
```
231+
232+
To use preceding component to demonstrate persisting the count of 10 in `CounterTracker.CurrentCount`, navigate to the component and refresh the browser, which triggers prerendering. When prerendering occurs, you briefly see <xref:Microsoft.AspNetCore.Components.RendererInfo.Name%2A?displayProperty=nameWithType> indicate "`Static`" before displaying "`Server`" after final rendering. The counter starts at 10.
233+
234+
## Use the `PersistentComponentState` service directly instead of the declarative model
181235

182236
As an alternative to using the declarative model for persisting state with the `[PersistentState]` attribute, you can use the <xref:Microsoft.AspNetCore.Components.PersistentComponentState> service directly, which offers greater flexibility for complex state persistence scenarios. Call <xref:Microsoft.AspNetCore.Components.PersistentComponentState.RegisterOnPersisting%2A?displayProperty=nameWithType> to register a callback to persist the component state during prerendering. The state is retrieved when the component renders interactively. Make the call at the end of initialization code in order to avoid a potential race condition during app shutdown.
183237

@@ -268,10 +322,6 @@ When the component executes, `currentCount` is only set once during prerendering
268322
269323
:::moniker-end
270324

271-
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.
272-
273-
The persisted prerendered state is transferred to the client, where it's used to restore the component state. During client-side rendering (CSR, `InteractiveWebAssembly`), the data is exposed to the browser and must not contain sensitive, private information. During interactive server-side rendering (interactive SSR, `InteractiveServer`), [ASP.NET Core Data Protection](xref:security/data-protection/introduction) ensures that the data is transferred securely. The `InteractiveAuto` render mode combines WebAssembly and Server interactivity, so it's necessary to consider data exposure to the browser, as in the CSR case.
274-
275325
:::moniker range=">= aspnetcore-10.0"
276326

277327
## Serialization extensibility for persistent component state

aspnetcore/blazor/state-management/server.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ An app can only persist *app state*. UIs can't be persisted, such as component i
5151

5252
## Circuit state persistence
5353

54+
<!-- UPDATE 10.0 - API doc cross-links -->
55+
5456
During server-side rendering, Blazor Web Apps can persist a user's session (circuit) state when the connection to the server is lost for an extended period of time or proactively paused, as long as a full-page refresh isn't triggered. This allows users to resume their session without losing unsaved work in the following scenarios:
5557

5658
* Browser tab throttling

0 commit comments

Comments
 (0)