Skip to content

Commit 2519ee8

Browse files
Copilotjaviercn
andcommitted
Implement core persistence reason interfaces and filtering logic
Co-authored-by: javiercn <[email protected]>
1 parent bae3489 commit 2519ee8

File tree

9 files changed

+215
-3
lines changed

9 files changed

+215
-3
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
/// <summary>
7+
/// Represents a reason for persisting component state.
8+
/// </summary>
9+
public interface IPersistenceReason
10+
{
11+
/// <summary>
12+
/// Gets a value indicating whether state should be persisted by default for this reason.
13+
/// </summary>
14+
bool PersistByDefault { get; }
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
/// <summary>
7+
/// Filters component state persistence based on the reason for persistence.
8+
/// </summary>
9+
public interface IPersistenceReasonFilter
10+
{
11+
/// <summary>
12+
/// Determines whether state should be persisted for the given reason.
13+
/// </summary>
14+
/// <param name="reason">The reason for persistence.</param>
15+
/// <returns><c>true</c> to persist state, <c>false</c> to skip persistence, or <c>null</c> to defer to other filters or default behavior.</returns>
16+
bool? ShouldPersist(IPersistenceReason reason);
17+
}

src/Components/Components/src/PersistComponentStateRegistration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ namespace Microsoft.AspNetCore.Components;
55

66
internal readonly struct PersistComponentStateRegistration(
77
Func<Task> callback,
8-
IComponentRenderMode? renderMode)
8+
IComponentRenderMode? renderMode,
9+
IReadOnlyList<IPersistenceReasonFilter>? reasonFilters = null)
910
{
1011
public Func<Task> Callback { get; } = callback;
1112

1213
public IComponentRenderMode? RenderMode { get; } = renderMode;
14+
15+
public IReadOnlyList<IPersistenceReasonFilter>? ReasonFilters { get; } = reasonFilters;
1316
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
/// <summary>
7+
/// Base class for filtering component state persistence based on specific persistence reasons.
8+
/// </summary>
9+
/// <typeparam name="TReason">The type of persistence reason this filter handles.</typeparam>
10+
public abstract class PersistReasonFilter<TReason> : Attribute, IPersistenceReasonFilter
11+
where TReason : IPersistenceReason
12+
{
13+
private readonly bool _persist;
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="PersistReasonFilter{TReason}"/> class.
17+
/// </summary>
18+
/// <param name="persist">Whether to persist state for the specified reason type.</param>
19+
protected PersistReasonFilter(bool persist)
20+
{
21+
_persist = persist;
22+
}
23+
24+
/// <inheritdoc />
25+
public bool? ShouldPersist(IPersistenceReason reason)
26+
{
27+
if (reason is TReason)
28+
{
29+
return _persist;
30+
}
31+
32+
return null;
33+
}
34+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
/// <summary>
7+
/// Represents persistence during prerendering.
8+
/// </summary>
9+
public class PersistOnPrerendering : IPersistenceReason
10+
{
11+
/// <inheritdoc />
12+
public bool PersistByDefault { get; } = true;
13+
}
14+
15+
/// <summary>
16+
/// Represents persistence during enhanced navigation.
17+
/// </summary>
18+
public class PersistOnEnhancedNavigation : IPersistenceReason
19+
{
20+
/// <inheritdoc />
21+
public bool PersistByDefault { get; }
22+
}
23+
24+
/// <summary>
25+
/// Represents persistence when a circuit is paused.
26+
/// </summary>
27+
public class PersistOnCircuitPause : IPersistenceReason
28+
{
29+
/// <inheritdoc />
30+
public bool PersistByDefault { get; } = true;
31+
}

src/Components/Components/src/PersistentComponentState.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ internal void InitializeExistingState(IDictionary<string, byte[]> existingState)
4545
public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> callback)
4646
=> RegisterOnPersisting(callback, null);
4747

48+
/// <summary>
49+
/// Register a callback to persist the component state when the application is about to be paused.
50+
/// Registered callbacks can use this opportunity to persist their state so that it can be retrieved when the application resumes.
51+
/// </summary>
52+
/// <param name="callback">The callback to invoke when the application is being paused.</param>
53+
/// <param name="renderMode"></param>
54+
/// <param name="reasonFilters">Filters to control when the callback should be invoked based on the persistence reason.</param>
55+
/// <returns>A subscription that can be used to unregister the callback when disposed.</returns>
56+
public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> callback, IComponentRenderMode? renderMode, IReadOnlyList<IPersistenceReasonFilter>? reasonFilters)
57+
{
58+
ArgumentNullException.ThrowIfNull(callback);
59+
60+
if (PersistingState)
61+
{
62+
throw new InvalidOperationException("Registering a callback while persisting state is not allowed.");
63+
}
64+
65+
var persistenceCallback = new PersistComponentStateRegistration(callback, renderMode, reasonFilters);
66+
67+
_registeredCallbacks.Add(persistenceCallback);
68+
69+
return new PersistingComponentStateSubscription(_registeredCallbacks, persistenceCallback);
70+
}
71+
4872
/// <summary>
4973
/// Register a callback to persist the component state when the application is about to be paused.
5074
/// Registered callbacks can use this opportunity to persist their state so that it can be retrieved when the application resumes.

src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ public async Task RestoreStateAsync(IPersistentComponentStateStore store)
6868
/// <param name="renderer">The <see cref="Renderer"/> that components are being rendered.</param>
6969
/// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
7070
public Task PersistStateAsync(IPersistentComponentStateStore store, Renderer renderer)
71+
=> PersistStateAsync(store, renderer, new PersistOnPrerendering());
72+
73+
/// <summary>
74+
/// Persists the component application state into the given <see cref="IPersistentComponentStateStore"/>.
75+
/// </summary>
76+
/// <param name="store">The <see cref="IPersistentComponentStateStore"/> to restore the application state from.</param>
77+
/// <param name="renderer">The <see cref="Renderer"/> that components are being rendered.</param>
78+
/// <param name="persistenceReason">The reason for persisting the state.</param>
79+
/// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
80+
public Task PersistStateAsync(IPersistentComponentStateStore store, Renderer renderer, IPersistenceReason persistenceReason)
7181
{
7282
if (_stateIsPersisted)
7383
{
@@ -113,7 +123,7 @@ async Task PauseAndPersistState()
113123

114124
async Task<bool> TryPersistState(IPersistentComponentStateStore store)
115125
{
116-
if (!await TryPauseAsync(store))
126+
if (!await TryPauseAsync(store, persistenceReason))
117127
{
118128
_currentState.Clear();
119129
return false;
@@ -159,7 +169,7 @@ private void InferRenderModes(Renderer renderer)
159169
var componentRenderMode = renderer.GetComponentRenderMode(component);
160170
if (componentRenderMode != null)
161171
{
162-
_registeredCallbacks[i] = new PersistComponentStateRegistration(registration.Callback, componentRenderMode);
172+
_registeredCallbacks[i] = new PersistComponentStateRegistration(registration.Callback, componentRenderMode, registration.ReasonFilters);
163173
}
164174
else
165175
{
@@ -177,6 +187,9 @@ private void InferRenderModes(Renderer renderer)
177187
}
178188

179189
internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store)
190+
=> TryPauseAsync(store, new PersistOnPrerendering());
191+
192+
internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store, IPersistenceReason persistenceReason)
180193
{
181194
List<Task<bool>>? pendingCallbackTasks = null;
182195

@@ -199,6 +212,27 @@ internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store)
199212
continue;
200213
}
201214

215+
// Evaluate reason filters to determine if the callback should be executed for this persistence reason
216+
if (registration.ReasonFilters != null)
217+
{
218+
var shouldPersist = EvaluateReasonFilters(registration.ReasonFilters, persistenceReason);
219+
if (shouldPersist.HasValue && !shouldPersist.Value)
220+
{
221+
// Filters explicitly indicate not to persist for this reason
222+
continue;
223+
}
224+
else if (!shouldPersist.HasValue && !persistenceReason.PersistByDefault)
225+
{
226+
// No filter matched and default is not to persist
227+
continue;
228+
}
229+
}
230+
else if (!persistenceReason.PersistByDefault)
231+
{
232+
// No filters defined and default is not to persist
233+
continue;
234+
}
235+
202236
var result = TryExecuteCallback(registration.Callback, _logger);
203237
if (!result.IsCompletedSuccessfully)
204238
{
@@ -271,4 +305,19 @@ static async Task<bool> AnyTaskFailed(List<Task<bool>> pendingCallbackTasks)
271305
return true;
272306
}
273307
}
308+
309+
private static bool? EvaluateReasonFilters(IReadOnlyList<IPersistenceReasonFilter> reasonFilters, IPersistenceReason persistenceReason)
310+
{
311+
foreach (var reasonFilter in reasonFilters)
312+
{
313+
var shouldPersist = reasonFilter.ShouldPersist(persistenceReason);
314+
if (shouldPersist.HasValue)
315+
{
316+
return shouldPersist.Value;
317+
}
318+
}
319+
320+
// No filter matched
321+
return null;
322+
}
274323
}

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,27 @@ Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.NotFoundEventArgs(stri
1010
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.Path.get -> string!
1111
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger, System.IServiceProvider! serviceProvider) -> void
1212
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.SetPlatformRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
13+
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.RenderTree.Renderer! renderer, Microsoft.AspNetCore.Components.IPersistenceReason! persistenceReason) -> System.Threading.Tasks.Task
1314
Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions
1415
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute
1516
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void
17+
Microsoft.AspNetCore.Components.IPersistenceReason
18+
Microsoft.AspNetCore.Components.IPersistenceReason.PersistByDefault.get -> bool
19+
Microsoft.AspNetCore.Components.IPersistenceReasonFilter
20+
Microsoft.AspNetCore.Components.IPersistenceReasonFilter.ShouldPersist(Microsoft.AspNetCore.Components.IPersistenceReason! reason) -> bool?
21+
Microsoft.AspNetCore.Components.PersistOnCircuitPause
22+
Microsoft.AspNetCore.Components.PersistOnCircuitPause.PersistByDefault.get -> bool
23+
Microsoft.AspNetCore.Components.PersistOnCircuitPause.PersistOnCircuitPause() -> void
24+
Microsoft.AspNetCore.Components.PersistOnEnhancedNavigation
25+
Microsoft.AspNetCore.Components.PersistOnEnhancedNavigation.PersistByDefault.get -> bool
26+
Microsoft.AspNetCore.Components.PersistOnEnhancedNavigation.PersistOnEnhancedNavigation() -> void
27+
Microsoft.AspNetCore.Components.PersistOnPrerendering
28+
Microsoft.AspNetCore.Components.PersistOnPrerendering.PersistByDefault.get -> bool
29+
Microsoft.AspNetCore.Components.PersistOnPrerendering.PersistOnPrerendering() -> void
30+
Microsoft.AspNetCore.Components.PersistReasonFilter<TReason>
31+
Microsoft.AspNetCore.Components.PersistReasonFilter<TReason>.PersistReasonFilter(bool persist) -> void
32+
Microsoft.AspNetCore.Components.PersistReasonFilter<TReason>.ShouldPersist(Microsoft.AspNetCore.Components.IPersistenceReason! reason) -> bool?
33+
Microsoft.AspNetCore.Components.PersistentComponentState.RegisterOnPersisting(System.Func<System.Threading.Tasks.Task!>! callback, Microsoft.AspNetCore.Components.IComponentRenderMode? renderMode, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.IPersistenceReasonFilter!>? reasonFilters) -> Microsoft.AspNetCore.Components.PersistingComponentStateSubscription
1634
Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
1735
static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration<TService>(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
1836
static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components;
5+
6+
namespace Microsoft.AspNetCore.Components.Web;
7+
8+
/// <summary>
9+
/// Filter that controls whether component state should be persisted during prerendering.
10+
/// </summary>
11+
public class PersistOnPrerenderingFilter(bool persist = true) : PersistReasonFilter<PersistOnPrerendering>(persist);
12+
13+
/// <summary>
14+
/// Filter that controls whether component state should be persisted during enhanced navigation.
15+
/// </summary>
16+
public class PersistOnEnhancedNavigationFilter(bool persist = true) : PersistReasonFilter<PersistOnEnhancedNavigation>(persist);
17+
18+
/// <summary>
19+
/// Filter that controls whether component state should be persisted when a circuit is paused.
20+
/// </summary>
21+
public class PersistOnCircuitPauseFilter(bool persist = true) : PersistReasonFilter<PersistOnCircuitPause>(persist);

0 commit comments

Comments
 (0)