Open
Description
Background and Motivation
We want to support choosing when properties annotated with PersistentComponentState should be restored as this is required for supporting PCS in enhanced navigation scenarios.
-
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.
-
There is a new RestoreContext and IRestoreContextFilter pair of types that is used to support choosing when we should skip restoring a value.
-
The logic is that if a subscription contains a filter that applies to a context the value is filtered according to the filter, otherwise the value is restored.
- Disabling restore on circuit resume should not interfere with restore on prerendering or enhanced navigation.
Proposed API
Microsoft.AspNetCore.Components
namespace Microsoft.AspNetCore.Components.Infrastructure
{
public class ComponentStatePersistenceManager
{
+ public Task RestoreStateAsync(IPersistentComponentStateStore store, RestoreContext restoreContext);
}
}
namespace Microsoft.AspNetCore.Components
{
public class PersistentComponentState
{
+ public RestoringComponentStateSubscription RegisterOnRestoring(Action callback, params Span<IRestoreContextFilter> filters);
}
+ public abstract class RestoreContext
+ {
+ bool IsRecurring { get; }
+ }
+ public interface IRestoreContextFilter
+ {
+ bool ShouldRestore(IRestoreContext scenario);
+ bool SupportsScenario(IRestoreContext scenario);
+ }
+ public class RestoringComponentStateSubscription : IDisposable
+ {
+ public RestoringComponentStateSubscription();
+ public void Dispose();
+ }
}
Microsoft.AspNetCore.Components.Web
+ namespace Microsoft.AspNetCore.Components
+ {
+ public class WebRestoreContextFilter : IRestoreContextFilter
+ {
+ public static WebRestoreContextFilter Prerendering { get; }
+ public static WebRestoreContextFilter Reconnection { get; }
+ public static WebRestoreContextFilter EnhancedNavigation { get; }
+ public bool ShouldRestore(IRestoreContext scenario);
+ public bool SupportsScenario(IRestoreContext scenario);
+ }
+ public class WebRestoreContext : IRestoreContext
+ {
+ public static WebRestoreContext Prerendering { get; }
+ public static WebRestoreContext Reconnection { get; }
+ public static WebRestoreContext EnhancedNavigation { get; }
+ public bool IsRecurring { get; }
+ }
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class RestoreStateOnPrerenderingAttribute : Attribute
+ {
+ public RestoreStateOnPrerenderingAttribute(bool enabled = true);
+ }
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class RestoreStateOnReconnectionAttribute : Attribute
+ {
+ public RestoreStateOnReconnectionAttribute(bool enabled = true);
+ }
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class UpdateStateOnEnhancedNavigation : Attribute
+ {
+ public UpdateStateOnEnhancedNavigation(bool enabled = false);
+ }
Usage Examples
@page "/clientfetchdata/"
@using BlazorUnitedApp.Client.Data
@using Microsoft.AspNetCore.Components.Web
@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 {
[PersistentState]
[UpdateStateOnEnhancedNavigation(true)]
public ClientWeatherForecast[]? Forecasts { get; set; }
public string Page { get; set; } = "";
protected override async Task OnInitializedAsync()
{
Forecasts ??= await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
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)
Open questions:
- Make some names more "agnostic"?
- HostStart vs "Prerendering".
- HostRestart vs "Resume".
- AllowUpdates/ReceiveUpdates vs "UpdateOnEnhancedNavigation".
WHY MAKE NAMES MORE AGNOSTIC?
- We don't want references to circuit, prerendering, enhanced navigation and so on to pollute 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 context. 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.
- Instead of attributes, we can have properties on
Risks
N/A