Skip to content

Commit f7a8552

Browse files
committed
Some ideas
1 parent 7bbfde0 commit f7a8552

17 files changed

+157
-340
lines changed

src/Components/Components/src/IPersistentComponentStateStore.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@ public interface IPersistentComponentStateStore
2020
/// <param name="state">The serialized state to persist.</param>
2121
/// <returns>A <see cref="Task" /> that completes when the state is persisted to disk.</returns>
2222
Task PersistStateAsync(IReadOnlyDictionary<string, byte[]> state);
23+
24+
/// <summary>
25+
/// Returns a value that indicates whether the store supports the given <see cref="PersistedStateSerializationMode"/>.
26+
/// </summary>
27+
/// <param name="serializationMode">The <see cref="PersistedStateSerializationMode"/> in question.</param>
28+
/// <returns><c>true</c> if the serialization mode is supported by the store, otherwise <c>false</c>.</returns>
29+
bool SupportsSerializationMode(PersistedStateSerializationMode serializationMode) => true;
2330
}

src/Components/Components/src/ISerializationModeHandler.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,4 @@ public interface ISerializationModeHandler
1414
/// <param name="callbackTarget">The callback target</param>
1515
/// <returns>The <see cref="PersistedStateSerializationMode"/> for the component.</returns>
1616
public PersistedStateSerializationMode GetCallbackTargetSerializationMode(object? callbackTarget);
17-
18-
/// <summary>
19-
///
20-
/// </summary>
21-
public PersistedStateSerializationMode GlobalSerializationMode { get; set; }
22-
2317
}

src/Components/Components/src/Infrastructure/ComponentStatePersistenceManager.cs

Lines changed: 29 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,19 @@ namespace Microsoft.AspNetCore.Components.Infrastructure;
1111
/// </summary>
1212
public class ComponentStatePersistenceManager
1313
{
14-
private bool _serverStateIsPersisted;
15-
private bool _webAssemblyStateIsPersisted;
16-
17-
private readonly List<Func<Task>> _serverCallbacks = new();
18-
private readonly List<Func<Task>> _webAssemblyCallbacks = new();
19-
20-
private readonly Dictionary<string, byte[]> _currentServerState = new(StringComparer.Ordinal);
21-
private readonly Dictionary<string, byte[]> _currentWebAssemblyState = new(StringComparer.Ordinal);
22-
23-
private readonly ISerializationModeHandler _serializationModeHandler;
24-
14+
private readonly List<PersistenceCallback> _registeredCallbacks = new();
2515
private readonly ILogger<ComponentStatePersistenceManager> _logger;
2616

17+
private bool _stateIsPersisted;
18+
2719
/// <summary>
2820
/// Initializes a new instance of <see cref="ComponentStatePersistenceManager"/>.
2921
/// </summary>
3022
public ComponentStatePersistenceManager(ILogger<ComponentStatePersistenceManager> logger, ISerializationModeHandler serializationModeHandler)
3123
{
32-
_serializationModeHandler = serializationModeHandler;
33-
34-
State = new PersistentComponentState(
35-
_currentServerState,
36-
_currentWebAssemblyState,
37-
_serverCallbacks,
38-
_webAssemblyCallbacks,
39-
_serializationModeHandler);
40-
4124
_logger = logger;
25+
26+
State = new(_registeredCallbacks, serializationModeHandler);
4227
}
4328

4429
/// <summary>
@@ -61,87 +46,54 @@ public async Task RestoreStateAsync(IPersistentComponentStateStore store)
6146
/// Persists the component application state into the given <see cref="IPersistentComponentStateStore"/>.
6247
/// </summary>
6348
/// <param name="store">The <see cref="IPersistentComponentStateStore"/> to persist the application state into.</param>
64-
/// <param name="serializationMode">The <see cref="PersistedStateSerializationMode"/> to persist the application state.</param>
6549
/// <param name="renderer">The <see cref="Renderer"/> that components are being rendered.</param>
6650
/// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
67-
public Task PersistStateAsync(
68-
IPersistentComponentStateStore store,
69-
PersistedStateSerializationMode serializationMode,
70-
Renderer renderer)
71-
=> PersistStateAsync(store, serializationMode, renderer.Dispatcher);
51+
public Task PersistStateAsync(IPersistentComponentStateStore store, Renderer renderer)
52+
=> PersistStateAsync(store, renderer.Dispatcher);
7253

7354
/// <summary>
7455
/// Persists the component application state into the given <see cref="IPersistentComponentStateStore"/>
7556
/// so that it could be restored on Server.
7657
/// </summary>
7758
/// <param name="store">The <see cref="IPersistentComponentStateStore"/> to persist the application state into.</param>
78-
/// <param name="serializationMode">The <see cref="PersistedStateSerializationMode"/> to persist the application state.</param>
7959
/// <param name="dispatcher">The <see cref="Dispatcher"/> corresponding to the components' renderer.</param>
8060
/// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
81-
public Task PersistStateAsync(
82-
IPersistentComponentStateStore store,
83-
PersistedStateSerializationMode serializationMode,
84-
Dispatcher dispatcher)
61+
public Task PersistStateAsync(IPersistentComponentStateStore store, Dispatcher dispatcher)
8562
{
86-
if (_serializationModeHandler.GlobalSerializationMode != PersistedStateSerializationMode.Infer &&
87-
serializationMode != _serializationModeHandler.GlobalSerializationMode)
63+
if (_stateIsPersisted)
8864
{
89-
throw new InvalidOperationException("Cannot persist state with the given serialization mode.");
65+
throw new InvalidOperationException("State already persisted.");
9066
}
9167

92-
switch (serializationMode)
93-
{
94-
case PersistedStateSerializationMode.Server:
95-
if (_serverStateIsPersisted)
96-
{
97-
throw new InvalidOperationException("State already persisted.");
98-
}
99-
_serverStateIsPersisted = true;
100-
return PersistStateAsync(store, serializationMode, _serverCallbacks, _currentServerState, dispatcher);
101-
102-
case PersistedStateSerializationMode.WebAssembly:
103-
if (_webAssemblyStateIsPersisted)
104-
{
105-
throw new InvalidOperationException("State already persisted.");
106-
}
107-
_webAssemblyStateIsPersisted = true;
108-
return PersistStateAsync(store, serializationMode, _webAssemblyCallbacks, _currentWebAssemblyState, dispatcher);
109-
110-
default:
111-
throw new InvalidOperationException("Invalid persistence mode");
112-
}
113-
}
68+
_stateIsPersisted = true;
11469

115-
private Task PersistStateAsync(
116-
IPersistentComponentStateStore store,
117-
PersistedStateSerializationMode serializationMode,
118-
List<Func<Task>> callbacks,
119-
Dictionary<string, byte[]> currentState,
120-
Dispatcher dispatcher)
121-
{
12270
return dispatcher.InvokeAsync(PauseAndPersistState);
12371

12472
async Task PauseAndPersistState()
12573
{
126-
State.PersistingState = true;
127-
State.CurrentSerializationMode = serializationMode;
74+
var currentState = new Dictionary<string, byte[]>();
12875

129-
await PauseAsync(callbacks);
130-
131-
State.PersistingState = false;
76+
State.PersistenceContext = new(currentState);
77+
await PauseAsync(store);
78+
State.PersistenceContext = default;
13279

13380
await store.PersistStateAsync(currentState);
13481
}
13582
}
13683

137-
internal async Task PauseAsync(List<Func<Task>> callbacks)
84+
internal Task PauseAsync(IPersistentComponentStateStore store)
13885
{
13986
List<Task>? pendingCallbackTasks = null;
14087

141-
for (var i = 0; i < callbacks.Count; i++)
88+
for (var i = 0; i < _registeredCallbacks.Count; i++)
14289
{
143-
var callback = callbacks[i];
144-
var result = ExecuteCallback(callback, _logger);
90+
var callback = _registeredCallbacks[i];
91+
if (!store.SupportsSerializationMode(callback.SerializationMode))
92+
{
93+
continue;
94+
}
95+
96+
var result = ExecuteCallback(callback.Callback, _logger);
14597
if (!result.IsCompletedSuccessfully)
14698
{
14799
pendingCallbackTasks ??= new();
@@ -151,7 +103,11 @@ internal async Task PauseAsync(List<Func<Task>> callbacks)
151103

152104
if (pendingCallbackTasks != null)
153105
{
154-
await Task.WhenAll(pendingCallbackTasks);
106+
return Task.WhenAll(pendingCallbackTasks);
107+
}
108+
else
109+
{
110+
return Task.CompletedTask;
155111
}
156112

157113
static Task ExecuteCallback(Func<Task> callback, ILogger<ComponentStatePersistenceManager> logger)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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+
internal readonly record struct PersistenceCallback(Func<Task> Callback, PersistedStateSerializationMode SerializationMode);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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+
internal readonly record struct PersistenceContext(IDictionary<string, byte[]> State);

src/Components/Components/src/PersistentComponentState.cs

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,20 @@ public class PersistentComponentState
1414
{
1515
private IDictionary<string, byte[]>? _existingState;
1616

17-
private readonly IDictionary<string, byte[]> _currentServerState;
18-
private readonly IDictionary<string, byte[]> _currentWebAssemblyState;
19-
20-
private readonly List<Func<Task>> _registeredServerCallbacks;
21-
private readonly List<Func<Task>> _registeredWebAssemblyCallbacks;
22-
17+
private readonly List<PersistenceCallback> _registeredCallbacks;
2318
private readonly ISerializationModeHandler _serializationModeHandler;
2419

25-
internal PersistentComponentState(
26-
IDictionary<string, byte[]> currentServerState,
27-
IDictionary<string, byte[]> currentWebAssemblyState,
28-
List<Func<Task>> pauseServerCallbacks,
29-
List<Func<Task>> pauseWebAssemblyCallbacks,
30-
ISerializationModeHandler serializationModeHandler)
20+
internal PersistenceContext? PersistenceContext { get; set; }
21+
22+
internal PersistentComponentState(List<PersistenceCallback> registeredCallbacks, ISerializationModeHandler serializationModeHandler)
3123
{
32-
_currentServerState = currentServerState;
33-
_currentWebAssemblyState = currentWebAssemblyState;
34-
_registeredServerCallbacks = pauseServerCallbacks;
35-
_registeredWebAssemblyCallbacks = pauseWebAssemblyCallbacks;
24+
_registeredCallbacks = registeredCallbacks;
3625
_serializationModeHandler = serializationModeHandler;
3726
}
3827

39-
internal bool PersistingState { get; set; }
40-
41-
internal PersistedStateSerializationMode CurrentSerializationMode { get; set; } = PersistedStateSerializationMode.Infer;
42-
4328
internal void InitializeExistingState(IDictionary<string, byte[]> existingState)
4429
{
45-
// The existing state is either Server or WebAsasembly
30+
// The existing state is either Server or WebAssembly
4631
_existingState = existingState ?? throw new ArgumentNullException(nameof(existingState));
4732
}
4833

@@ -72,22 +57,10 @@ public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> call
7257
{
7358
ArgumentNullException.ThrowIfNull(callback);
7459

75-
if (_serializationModeHandler.GlobalSerializationMode != PersistedStateSerializationMode.Infer &&
76-
serializationMode != _serializationModeHandler.GlobalSerializationMode)
77-
{
78-
throw new InvalidOperationException("Cannot register a callback with the given serialization mode.");
79-
}
80-
81-
var registeredCallbacks = serializationMode switch
82-
{
83-
PersistedStateSerializationMode.Server => _registeredServerCallbacks,
84-
PersistedStateSerializationMode.WebAssembly => _registeredWebAssemblyCallbacks,
85-
_ => throw new InvalidOperationException("Invalid persistence mode.")
86-
};
87-
88-
registeredCallbacks.Add(callback);
60+
var persistenceCallback = new PersistenceCallback(callback, serializationMode);
61+
_registeredCallbacks.Add(persistenceCallback);
8962

90-
return new PersistingComponentStateSubscription(registeredCallbacks, callback);
63+
return new PersistingComponentStateSubscription(_registeredCallbacks, persistenceCallback);
9164
}
9265

9366
/// <summary>
@@ -99,16 +72,7 @@ public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> call
9972
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")]
10073
public void PersistAsJson<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string key, TValue instance)
10174
{
102-
var currentState = CurrentSerializationMode switch
103-
{
104-
PersistedStateSerializationMode.Server => _currentServerState,
105-
PersistedStateSerializationMode.WebAssembly => _currentWebAssemblyState,
106-
_ => throw new InvalidOperationException("Invalid persistence mode.")
107-
};
108-
109-
ArgumentNullException.ThrowIfNull(key);
110-
111-
if (!PersistingState)
75+
if (PersistenceContext is not { State: var currentState })
11276
{
11377
throw new InvalidOperationException("Persisting state is only allowed during an OnPersisting callback.");
11478
}

src/Components/Components/src/PersistingComponentStateSubscription.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ namespace Microsoft.AspNetCore.Components;
1111
/// </summary>
1212
public readonly struct PersistingComponentStateSubscription : IDisposable
1313
{
14-
private readonly List<Func<Task>>? _callbacks;
15-
private readonly Func<Task>? _callback;
14+
private readonly List<PersistenceCallback>? _callbacks;
15+
private readonly PersistenceCallback? _callback;
1616

17-
internal PersistingComponentStateSubscription(List<Func<Task>> callbacks, Func<Task> callback)
17+
internal PersistingComponentStateSubscription(List<PersistenceCallback> callbacks, PersistenceCallback callback)
1818
{
1919
_callbacks = callbacks;
2020
_callback = callback;
@@ -23,9 +23,9 @@ internal PersistingComponentStateSubscription(List<Func<Task>> callbacks, Func<T
2323
/// <inheritdoc />
2424
public void Dispose()
2525
{
26-
if (_callback != null)
26+
if (_callback.HasValue)
2727
{
28-
_callbacks?.Remove(_callback);
28+
_callbacks?.Remove(_callback.Value);
2929
}
3030
}
3131
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ Microsoft.AspNetCore.Components.IComponentRenderMode
1919
*REMOVED*Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger) -> void
2020
*REMOVED*Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.RenderTree.Renderer! renderer) -> System.Threading.Tasks.Task!
2121
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger, Microsoft.AspNetCore.Components.ISerializationModeHandler! serializationModeHandler) -> void
22-
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.PersistedStateSerializationMode serializationMode, Microsoft.AspNetCore.Components.Dispatcher! dispatcher) -> System.Threading.Tasks.Task!
23-
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.PersistedStateSerializationMode serializationMode, Microsoft.AspNetCore.Components.RenderTree.Renderer! renderer) -> System.Threading.Tasks.Task!
22+
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.Dispatcher! dispatcher) -> System.Threading.Tasks.Task!
23+
Microsoft.AspNetCore.Components.IPersistentComponentStateStore.SupportsSerializationMode(Microsoft.AspNetCore.Components.PersistedStateSerializationMode serializationMode) -> bool
2424
Microsoft.AspNetCore.Components.ISerializationModeHandler
2525
Microsoft.AspNetCore.Components.ISerializationModeHandler.GetCallbackTargetSerializationMode(object? callbackTarget) -> Microsoft.AspNetCore.Components.PersistedStateSerializationMode
2626
Microsoft.AspNetCore.Components.ISerializationModeHandler.GlobalSerializationMode.get -> Microsoft.AspNetCore.Components.PersistedStateSerializationMode

src/Components/Components/test/Lifetime/ComponentApplicationLifetimeTest.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public async Task PersistStateAsync_SavesPersistedStateToTheStore(PersistedState
6262
}, serializationMode);
6363

6464
// Act
65-
await lifetime.PersistStateAsync(store, serializationMode, renderer);
65+
await lifetime.PersistStateAsync(store, renderer);
6666

6767
// Assert
6868
Assert.True(store.State.TryGetValue("MyState", out var persisted));
@@ -87,7 +87,7 @@ public async Task PersistStateAsync_InvokesPauseCallbacksDuringPersist(Persisted
8787
lifetime.State.RegisterOnPersisting(() => { invoked = true; return default; }, serializationMode);
8888

8989
// Act
90-
await lifetime.PersistStateAsync(store, serializationMode, renderer);
90+
await lifetime.PersistStateAsync(store, renderer);
9191

9292
// Assert
9393
Assert.True(invoked);
@@ -115,7 +115,7 @@ public async Task PersistStateAsync_FiresCallbacksInParallel(PersistedStateSeria
115115
lifetime.State.RegisterOnPersisting(async () => { sequence.Add(2); await tcs2.Task; sequence.Add(4); }, serializationMode);
116116

117117
// Act
118-
var persistTask = lifetime.PersistStateAsync(store, serializationMode, renderer);
118+
var persistTask = lifetime.PersistStateAsync(store, renderer);
119119
tcs.SetResult();
120120
tcs2.SetResult();
121121

@@ -150,7 +150,7 @@ public async Task PersistStateAsync_CallbacksAreRemovedWhenSubscriptionsAreDispo
150150
subscription1.Dispose();
151151
subscription2.Dispose();
152152

153-
var persistTask = lifetime.PersistStateAsync(store, serializationMode, renderer);
153+
var persistTask = lifetime.PersistStateAsync(store, renderer);
154154
tcs.SetResult();
155155
tcs2.SetResult();
156156

@@ -180,7 +180,7 @@ public async Task PersistStateAsync_ContinuesInvokingCallbacksDuringPersistIfACa
180180
lifetime.State.RegisterOnPersisting(() => { invoked = true; return Task.CompletedTask; }, serializationMode);
181181

182182
// Act
183-
await lifetime.PersistStateAsync(store, serializationMode, renderer);
183+
await lifetime.PersistStateAsync(store, renderer);
184184

185185
// Assert
186186
Assert.True(invoked);
@@ -208,7 +208,7 @@ public async Task PersistStateAsync_ContinuesInvokingCallbacksDuringPersistIfACa
208208
lifetime.State.RegisterOnPersisting(() => { invoked = true; return Task.CompletedTask; }, serializationMode);
209209

210210
// Act
211-
var persistTask = lifetime.PersistStateAsync(store,serializationMode, renderer);
211+
var persistTask = lifetime.PersistStateAsync(store, renderer);
212212
tcs.SetResult();
213213

214214
await persistTask;
@@ -241,10 +241,10 @@ public async Task PersistStateAsync_ThrowsWhenDeveloperTriesToPersistStateMultip
241241
}, serializationMode);
242242

243243
// Act
244-
await lifetime.PersistStateAsync(store, serializationMode, renderer);
244+
await lifetime.PersistStateAsync(store, renderer);
245245

246246
// Assert
247-
await Assert.ThrowsAsync<InvalidOperationException>(() => lifetime.PersistStateAsync(store, serializationMode, renderer));
247+
await Assert.ThrowsAsync<InvalidOperationException>(() => lifetime.PersistStateAsync(store, renderer));
248248
}
249249

250250
private class TestRenderer : Renderer
@@ -299,12 +299,6 @@ public TestComponentSerializationModeHandler(PersistedStateSerializationMode ser
299299
_serializationMode = serializationMode;
300300
}
301301

302-
public PersistedStateSerializationMode GlobalSerializationMode
303-
{
304-
get => _serializationMode;
305-
set => throw new NotImplementedException();
306-
}
307-
308302
public PersistedStateSerializationMode GetCallbackTargetSerializationMode(object callbackTarget)
309303
{
310304
return _serializationMode;

0 commit comments

Comments
 (0)