-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Background and Motivation
We want to support choosing when properties annotated with PersistentComponentState should be restored as this is required for supporting persistent component state (PCS) in enhanced navigation scenarios.
Currently:
- Prerendering and Resume work by default
- Apps might want to restore state only during prerendering or only during resume
- Enhanced navigation updates are off by default
- They can override existing user state, which is not good
- The app needs to opt-in to receive updates for individual values
The new RestoreContext and RestoreOptions types support choosing when to restore values. The RestoreContext provides three scenarios: InitialValue (when the host starts), LastSnapshot (when reconnecting), and ValueUpdate (for enhanced navigation updates). The RestoreOptions allows configuring the RestoreBehavior and whether to allow updates.
Proposed API
namespace Microsoft.AspNetCore.Components
{
public class PersistentComponentState
{
+ public RestoringComponentStateSubscription RegisterOnRestoring(Action callback, RestoreOptions options);
}
+ public readonly struct RestoreOptions
+ {
+ public RestoreOptions();
+ public RestoreBehavior RestoreBehavior { get; init; }
+ public bool AllowUpdates { get; init; }
+ }
+ [Flags]
+ public enum RestoreBehavior
+ {
+ Default = 0,
+ SkipInitialValue = 1,
+ SkipLastSnapshot = 2
+ }
+ public sealed class RestoreContext
+ {
+ public static RestoreContext InitialValue { get; }
+ public static RestoreContext LastSnapshot { get; }
+ public static RestoreContext ValueUpdate { get; }
+ }
+ public readonly struct RestoringComponentStateSubscription : IDisposable
+ {
+ public RestoringComponentStateSubscription();
+ public void Dispose();
+ }
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class PersistentStateAttribute : CascadingParameterAttribute
{
+ public RestoreBehavior RestoreBehavior { get; set; }
+ public bool AllowUpdates { get; set; }
}
}
namespace Microsoft.AspNetCore.Components.Infrastructure
{
public class ComponentStatePersistenceManager
{
+ public Task RestoreStateAsync(IPersistentComponentStateStore store, RestoreContext context);
}
}
namespace Microsoft.AspNetCore.Components.Rendering
{
public class ComponentState : IAsyncDisposable
{
+ protected internal Renderer Renderer { get; }
}
}
Usage Examples
@page "/clientfetchdata/"
@using BlazorUnitedApp.Client.Data
@using Microsoft.AspNetCore.Components
@inject ClientWeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (Forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in Forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
// Persist state and allow updates during enhanced navigation
[PersistentState(AllowUpdates = true)]
public ClientWeatherForecast[]? Forecasts { get; set; }
public string Page { get; set; } = "";
protected override async Task OnInitializedAsync()
{
Forecasts ??= await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
}
Additional examples showing different restore behaviors:
// Skip restoring on initial load but allow on reconnection
[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string UserDraftData { get; set; }
// Skip restoring on reconnection but allow on initial load
[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public string InitialSettings { get; set; }
// Allow updates from enhanced navigation
[PersistentState(AllowUpdates = true)]
public List<TodoItem> TodoItems { get; set; }
// Programmatic restoration with custom options
protected override void OnInitialized()
{
var options = new RestoreOptions
{
RestoreBehavior = RestoreBehavior.Default,
AllowUpdates = true
};
State.RegisterOnRestoring(() =>
{
// Custom restoration logic
LoadCustomData();
}, options);
}
Alternative Designs
Fold enhanced navigation into RestoreStateOnPrerendering
and do not have enabled/disabled ctor, and instead only provide an attribute for non-defaults:
public sealed class DisableRestoreStateOnResumeAttribute
public sealed class DisableRestoreStateOnPrerenderingAttribute
public sealed class RestoreStateOnPrerendering(UpdateOnEnhancedNavigation = true)
We considered making names more "agnostic" to avoid references to circuit, prerendering, enhanced navigation in the Microsoft.AspNetCore.Components
assembly. Choosing more agnostic terms allows us to avoid those problematic terms and potentially simplify the design and implementation in the following ways:
- Instead of attributes, we can have properties on
PersistentState
: HostStart, HostResume, AllowUpdates - All the types in
Components.Web
can live onComponents
- We can think of a day where we "resume" the host on other contexts. For example, if we also start supporting tearing down and restarting wasm apps on demand, or if we support an auto policy where we tear down the server components and restart them on wasm without reload
Risks
N/A