Skip to content

Commit 7a29927

Browse files
committed
Cleanups and refined implementation
1 parent 235a84a commit 7a29927

File tree

6 files changed

+173
-60
lines changed

6 files changed

+173
-60
lines changed

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ public class ComponentStatePersistenceManager
1414
private readonly List<RegistrationContext> _registeredCallbacks = new();
1515
private readonly ILogger<ComponentStatePersistenceManager> _logger;
1616

17+
private bool _stateIsPersisted;
18+
private readonly Dictionary<string, byte[]> _currentState = new(StringComparer.Ordinal);
19+
1720
/// <summary>
1821
/// Initializes a new instance of <see cref="ComponentStatePersistenceManager"/>.
1922
/// </summary>
2023
public ComponentStatePersistenceManager(ILogger<ComponentStatePersistenceManager> logger)
2124
{
22-
State = new PersistentComponentState(_registeredCallbacks);
25+
State = new PersistentComponentState(_currentState, _registeredCallbacks);
2326
_logger = logger;
2427
}
2528

@@ -47,32 +50,45 @@ public async Task RestoreStateAsync(IPersistentComponentStateStore store)
4750
/// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
4851
public Task PersistStateAsync(IPersistentComponentStateStore store, Renderer renderer)
4952
{
53+
if (_stateIsPersisted)
54+
{
55+
throw new InvalidOperationException("State already persisted.");
56+
}
57+
5058
return renderer.Dispatcher.InvokeAsync(PauseAndPersistState);
5159

5260
async Task PauseAndPersistState()
5361
{
54-
InferRenderModes(renderer);
62+
State.PersistingState = true;
5563

5664
if (store is IEnumerable<IPersistentComponentStateStore> compositeStore)
5765
{
66+
// We only need to do inference when there is more than one store. This is determined by
67+
// the set of rendered components.
68+
InferRenderModes(renderer);
69+
70+
// Iterate over each store and give it a chance to run against the existing declared
71+
// render modes. After we've run through a store, we clear the current state so that
72+
// the next store can start with a clean slate.
5873
foreach (var store in compositeStore)
5974
{
6075
await PersistState(store);
76+
_currentState.Clear();
6177
}
6278
}
6379
else
6480
{
6581
await PersistState(store);
6682
}
83+
84+
State.PersistingState = false;
85+
_stateIsPersisted = true;
6786
}
6887

6988
async Task PersistState(IPersistentComponentStateStore store)
7089
{
71-
var currentState = new Dictionary<string, byte[]>();
72-
State.PersistenceContext = new(currentState);
73-
7490
await PauseAsync(store);
75-
await store.PersistStateAsync(currentState);
91+
await store.PersistStateAsync(_currentState);
7692
}
7793
}
7894

src/Components/Components/src/PersistenceContext.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/Components/Components/src/PersistentComponentState.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ namespace Microsoft.AspNetCore.Components;
1313
public class PersistentComponentState
1414
{
1515
private IDictionary<string, byte[]>? _existingState;
16-
17-
internal PersistenceContext? PersistenceContext { get; set; }
16+
private readonly IDictionary<string, byte[]> _currentState;
1817

1918
private readonly List<RegistrationContext> _registeredCallbacks;
2019

2120
internal PersistentComponentState(
21+
IDictionary<string , byte[]> currentState,
2222
List<RegistrationContext> pauseCallbacks)
2323
{
24+
_currentState = currentState;
2425
_registeredCallbacks = pauseCallbacks;
2526
}
2627

28+
internal bool PersistingState { get; set; }
29+
2730
internal void InitializeExistingState(IDictionary<string, byte[]> existingState)
2831
{
2932
if (_existingState != null)
@@ -71,17 +74,17 @@ public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> call
7174
{
7275
ArgumentNullException.ThrowIfNull(key);
7376

74-
if (PersistenceContext is not { State: var currentState })
77+
if (!PersistingState)
7578
{
7679
throw new InvalidOperationException("Persisting state is only allowed during an OnPersisting callback.");
7780
}
7881

79-
if (currentState.ContainsKey(key))
82+
if (_currentState.ContainsKey(key))
8083
{
8184
throw new ArgumentException($"There is already a persisted object under the same key '{key}'");
8285
}
8386

84-
currentState.Add(key, JsonSerializer.SerializeToUtf8Bytes(instance, JsonSerializerOptionsProvider.Options));
87+
_currentState.Add(key, JsonSerializer.SerializeToUtf8Bytes(instance, JsonSerializerOptionsProvider.Options));
8588
}
8689

8790
/// <summary>

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers;
5+
using System.Collections;
56
using System.Text.Json;
67
using Microsoft.AspNetCore.Components.Infrastructure;
78
using Microsoft.AspNetCore.Components.RenderTree;
@@ -41,7 +42,7 @@ public async Task RestoreStateAsync_ThrowsOnDoubleInitialization()
4142
// Arrange
4243
var state = new Dictionary<string, byte[]>
4344
{
44-
["MyState"] = new byte[] { 0, 1, 2, 3, 4 }
45+
["MyState"] = [0, 1, 2, 3, 4]
4546
};
4647
var store = new TestStore(state);
4748
var lifetime = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
@@ -57,7 +58,7 @@ public async Task PersistStateAsync_ThrowsWhenCallbackRenerModeCannotBeInferred(
5758
{
5859
// Arrange
5960
var state = new Dictionary<string, byte[]>();
60-
var store = new TestStore(state);
61+
var store = new CompositeTestStore(state);
6162
var lifetime = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
6263

6364
var renderer = new TestRenderer();
@@ -86,7 +87,7 @@ public async Task PersistStateAsync_SavesPersistedStateToTheStore()
8687
var data = new byte[] { 1, 2, 3, 4 };
8788

8889
lifetime.State.RegisterOnPersisting(() =>
89-
{
90+
{
9091
lifetime.State.PersistAsJson("MyState", new byte[] { 1, 2, 3, 4 });
9192
return Task.CompletedTask;
9293
}, new TestRenderMode());
@@ -276,6 +277,40 @@ public Task PersistStateAsync(IReadOnlyDictionary<string, byte[]> state)
276277
}
277278
}
278279

280+
private class CompositeTestStore : IPersistentComponentStateStore, IEnumerable<IPersistentComponentStateStore>
281+
{
282+
public CompositeTestStore(IDictionary<string, byte[]> initialState)
283+
{
284+
State = initialState;
285+
}
286+
287+
public IDictionary<string, byte[]> State { get; set; }
288+
289+
public IEnumerator<IPersistentComponentStateStore> GetEnumerator()
290+
{
291+
yield return new TestStore(State);
292+
yield return new TestStore(State);
293+
}
294+
295+
public Task<IDictionary<string, byte[]>> GetPersistedStateAsync()
296+
{
297+
return Task.FromResult(State);
298+
}
299+
300+
public Task PersistStateAsync(IReadOnlyDictionary<string, byte[]> state)
301+
{
302+
// We copy the data here because it's no longer available after this call completes.
303+
State = state.ToDictionary(k => k.Key, v => v.Value);
304+
return Task.CompletedTask;
305+
}
306+
307+
IEnumerator IEnumerable.GetEnumerator()
308+
{
309+
return GetEnumerator();
310+
}
311+
}
312+
313+
279314
private class TestRenderMode : IComponentRenderMode
280315
{
281316

src/Components/Components/test/Lifetime/ComponentApplicationStateTest.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ComponentApplicationStateTest
1111
public void InitializeExistingState_SetupsState()
1212
{
1313
// Arrange
14-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
14+
var applicationState = new PersistentComponentState(new Dictionary<string, byte[]>(), new List<RegistrationContext>());
1515
var existingState = new Dictionary<string, byte[]>
1616
{
1717
["MyState"] = JsonSerializer.SerializeToUtf8Bytes(new byte[] { 1, 2, 3, 4 })
@@ -29,7 +29,7 @@ public void InitializeExistingState_SetupsState()
2929
public void InitializeExistingState_ThrowsIfAlreadyInitialized()
3030
{
3131
// Arrange
32-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
32+
var applicationState = new PersistentComponentState(new Dictionary<string, byte[]>(), new List<RegistrationContext>());
3333
var existingState = new Dictionary<string, byte[]>
3434
{
3535
["MyState"] = new byte[] { 1, 2, 3, 4 }
@@ -45,7 +45,7 @@ public void InitializeExistingState_ThrowsIfAlreadyInitialized()
4545
public void TryRetrieveState_ReturnsStateWhenItExists()
4646
{
4747
// Arrange
48-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
48+
var applicationState = new PersistentComponentState(new Dictionary<string, byte[]>(), new List<RegistrationContext>());
4949
var existingState = new Dictionary<string, byte[]>
5050
{
5151
["MyState"] = JsonSerializer.SerializeToUtf8Bytes(new byte[] { 1, 2, 3, 4 })
@@ -64,9 +64,11 @@ public void TryRetrieveState_ReturnsStateWhenItExists()
6464
public void PersistState_SavesDataToTheStoreAsync()
6565
{
6666
// Arrange
67-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
6867
var currentState = new Dictionary<string, byte[]>();
69-
applicationState.PersistenceContext = new(currentState);
68+
var applicationState = new PersistentComponentState(currentState, new List<RegistrationContext>())
69+
{
70+
PersistingState = true
71+
};
7072
var myState = new byte[] { 1, 2, 3, 4 };
7173

7274
// Act
@@ -81,9 +83,11 @@ public void PersistState_SavesDataToTheStoreAsync()
8183
public void PersistState_ThrowsForDuplicateKeys()
8284
{
8385
// Arrange
84-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
8586
var currentState = new Dictionary<string, byte[]>();
86-
applicationState.PersistenceContext = new(currentState);
87+
var applicationState = new PersistentComponentState(currentState, new List<RegistrationContext>())
88+
{
89+
PersistingState = true
90+
};
8791
var myState = new byte[] { 1, 2, 3, 4 };
8892

8993
applicationState.PersistAsJson("MyState", myState);
@@ -96,9 +100,11 @@ public void PersistState_ThrowsForDuplicateKeys()
96100
public void PersistAsJson_SerializesTheDataToJsonAsync()
97101
{
98102
// Arrange
99-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
100103
var currentState = new Dictionary<string, byte[]>();
101-
applicationState.PersistenceContext = new(currentState);
104+
var applicationState = new PersistentComponentState(currentState, new List<RegistrationContext>())
105+
{
106+
PersistingState = true
107+
};
102108
var myState = new byte[] { 1, 2, 3, 4 };
103109

104110
// Act
@@ -113,9 +119,11 @@ public void PersistAsJson_SerializesTheDataToJsonAsync()
113119
public void PersistAsJson_NullValueAsync()
114120
{
115121
// Arrange
116-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
117122
var currentState = new Dictionary<string, byte[]>();
118-
applicationState.PersistenceContext = new(currentState);
123+
var applicationState = new PersistentComponentState(currentState, new List<RegistrationContext>())
124+
{
125+
PersistingState = true
126+
};
119127

120128
// Act
121129
applicationState.PersistAsJson<byte[]>("MyState", null);
@@ -132,7 +140,7 @@ public void TryRetrieveFromJson_DeserializesTheDataFromJson()
132140
var myState = new byte[] { 1, 2, 3, 4 };
133141
var serialized = JsonSerializer.SerializeToUtf8Bytes(myState);
134142
var existingState = new Dictionary<string, byte[]>() { ["MyState"] = serialized };
135-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
143+
var applicationState = new PersistentComponentState(new Dictionary<string, byte[]>(), new List<RegistrationContext>());
136144

137145
applicationState.InitializeExistingState(existingState);
138146

@@ -150,7 +158,7 @@ public void TryRetrieveFromJson_NullValue()
150158
// Arrange
151159
var serialized = JsonSerializer.SerializeToUtf8Bytes<byte[]>(null);
152160
var existingState = new Dictionary<string, byte[]>() { ["MyState"] = serialized };
153-
var applicationState = new PersistentComponentState(new List<RegistrationContext>());
161+
var applicationState = new PersistentComponentState(new Dictionary<string, byte[]>(), new List<RegistrationContext>());
154162

155163
applicationState.InitializeExistingState(existingState);
156164

0 commit comments

Comments
 (0)