2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System ;
5
+ using System . Buffers ;
5
6
using System . Collections . Generic ;
7
+ using System . Linq ;
6
8
using System . Threading . Tasks ;
7
- using Microsoft . AspNetCore . Components . Lifetime ;
9
+ using Microsoft . AspNetCore . Components . Infrastructure ;
8
10
using Microsoft . AspNetCore . Components . RenderTree ;
9
11
using Microsoft . Extensions . DependencyInjection ;
10
12
using Microsoft . Extensions . Logging ;
@@ -20,19 +22,19 @@ public class ComponentApplicationLifetimeTest
20
22
public async Task RestoreStateAsync_InitializesStateWithDataFromTheProvidedStore ( )
21
23
{
22
24
// Arrange
23
- byte [ ] data = new byte [ ] { 0 , 1 , 2 , 3 , 4 } ;
24
- var state = new Dictionary < string , byte [ ] >
25
+ var data = new ReadOnlySequence < byte > ( new byte [ ] { 0 , 1 , 2 , 3 , 4 } ) ;
26
+ var state = new Dictionary < string , ReadOnlySequence < byte > >
25
27
{
26
28
[ "MyState" ] = data
27
29
} ;
28
30
var store = new TestStore ( state ) ;
29
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
31
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
30
32
31
33
// Act
32
34
await lifetime . RestoreStateAsync ( store ) ;
33
35
34
36
// Assert
35
- Assert . True ( lifetime . State . TryTakePersistedState ( "MyState" , out var retrieved ) ) ;
37
+ Assert . True ( lifetime . State . TryTake ( "MyState" , out var retrieved ) ) ;
36
38
Assert . Empty ( state ) ;
37
39
Assert . Equal ( data , retrieved ) ;
38
40
}
@@ -41,12 +43,12 @@ public async Task RestoreStateAsync_InitializesStateWithDataFromTheProvidedStore
41
43
public async Task RestoreStateAsync_ThrowsOnDoubleInitialization ( )
42
44
{
43
45
// Arrange
44
- var state = new Dictionary < string , byte [ ] >
46
+ var state = new Dictionary < string , ReadOnlySequence < byte > >
45
47
{
46
- [ "MyState" ] = new byte [ ] { 0 , 1 , 2 , 3 , 4 }
48
+ [ "MyState" ] = new ReadOnlySequence < byte > ( new byte [ ] { 0 , 1 , 2 , 3 , 4 } )
47
49
} ;
48
50
var store = new TestStore ( state ) ;
49
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
51
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
50
52
51
53
await lifetime . RestoreStateAsync ( store ) ;
52
54
@@ -58,35 +60,39 @@ public async Task RestoreStateAsync_ThrowsOnDoubleInitialization()
58
60
public async Task PersistStateAsync_SavesPersistedStateToTheStore ( )
59
61
{
60
62
// Arrange
61
- var state = new Dictionary < string , byte [ ] > ( ) ;
63
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
62
64
var store = new TestStore ( state ) ;
63
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
65
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
64
66
65
67
var renderer = new TestRenderer ( ) ;
66
68
var data = new byte [ ] { 1 , 2 , 3 , 4 } ;
67
69
68
- lifetime . State . PersistState ( "MyState" , new byte [ ] { 1 , 2 , 3 , 4 } ) ;
70
+ lifetime . State . RegisterOnPersisting ( ( ) =>
71
+ {
72
+ lifetime . State . Persist ( "MyState" , writer => writer . Write ( new byte [ ] { 1 , 2 , 3 , 4 } ) ) ;
73
+ return Task . CompletedTask ;
74
+ } ) ;
69
75
70
76
// Act
71
77
await lifetime . PersistStateAsync ( store , renderer ) ;
72
78
73
79
// Assert
74
80
Assert . True ( store . State . TryGetValue ( "MyState" , out var persisted ) ) ;
75
- Assert . Equal ( data , persisted ) ;
81
+ Assert . Equal ( data , persisted . ToArray ( ) ) ;
76
82
}
77
83
78
84
[ Fact ]
79
85
public async Task PersistStateAsync_InvokesPauseCallbacksDuringPersist ( )
80
86
{
81
87
// Arrange
82
- var state = new Dictionary < string , byte [ ] > ( ) ;
88
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
83
89
var store = new TestStore ( state ) ;
84
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
90
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
85
91
var renderer = new TestRenderer ( ) ;
86
92
var data = new byte [ ] { 1 , 2 , 3 , 4 } ;
87
93
var invoked = false ;
88
94
89
- lifetime . State . OnPersisting += ( ) => { invoked = true ; return default ; } ;
95
+ lifetime . State . RegisterOnPersisting ( ( ) => { invoked = true ; return default ; } ) ;
90
96
91
97
// Act
92
98
await lifetime . PersistStateAsync ( store , renderer ) ;
@@ -99,18 +105,18 @@ public async Task PersistStateAsync_InvokesPauseCallbacksDuringPersist()
99
105
public async Task PersistStateAsync_FiresCallbacksInParallel ( )
100
106
{
101
107
// Arrange
102
- var state = new Dictionary < string , byte [ ] > ( ) ;
108
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
103
109
var store = new TestStore ( state ) ;
104
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
110
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
105
111
var renderer = new TestRenderer ( ) ;
106
112
107
113
var sequence = new List < int > { } ;
108
114
109
115
var tcs = new TaskCompletionSource ( ) ;
110
116
var tcs2 = new TaskCompletionSource ( ) ;
111
117
112
- lifetime . State . OnPersisting += async ( ) => { sequence . Add ( 1 ) ; await tcs . Task ; sequence . Add ( 3 ) ; } ;
113
- lifetime . State . OnPersisting += async ( ) => { sequence . Add ( 2 ) ; await tcs2 . Task ; sequence . Add ( 4 ) ; } ;
118
+ lifetime . State . RegisterOnPersisting ( async ( ) => { sequence . Add ( 1 ) ; await tcs . Task ; sequence . Add ( 3 ) ; } ) ;
119
+ lifetime . State . RegisterOnPersisting ( async ( ) => { sequence . Add ( 2 ) ; await tcs2 . Task ; sequence . Add ( 4 ) ; } ) ;
114
120
115
121
// Act
116
122
var persistTask = lifetime . PersistStateAsync ( store , renderer ) ;
@@ -123,22 +129,53 @@ public async Task PersistStateAsync_FiresCallbacksInParallel()
123
129
Assert . Equal ( new [ ] { 1 , 2 , 3 , 4 } , sequence ) ;
124
130
}
125
131
132
+ [ Fact ]
133
+ public async Task PersistStateAsync_CallbacksAreRemovedWhenSubscriptionsAreDisposed ( )
134
+ {
135
+ // Arrange
136
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
137
+ var store = new TestStore ( state ) ;
138
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
139
+ var renderer = new TestRenderer ( ) ;
140
+
141
+ var sequence = new List < int > { } ;
142
+
143
+ var tcs = new TaskCompletionSource ( ) ;
144
+ var tcs2 = new TaskCompletionSource ( ) ;
145
+
146
+ var subscription1 = lifetime . State . RegisterOnPersisting ( async ( ) => { sequence . Add ( 1 ) ; await tcs . Task ; sequence . Add ( 3 ) ; } ) ;
147
+ var subscription2 = lifetime . State . RegisterOnPersisting ( async ( ) => { sequence . Add ( 2 ) ; await tcs2 . Task ; sequence . Add ( 4 ) ; } ) ;
148
+
149
+ // Act
150
+ subscription1 . Dispose ( ) ;
151
+ subscription2 . Dispose ( ) ;
152
+
153
+ var persistTask = lifetime . PersistStateAsync ( store , renderer ) ;
154
+ tcs . SetResult ( ) ;
155
+ tcs2 . SetResult ( ) ;
156
+
157
+ await persistTask ;
158
+
159
+ // Assert
160
+ Assert . Empty ( sequence ) ;
161
+ }
162
+
126
163
[ Fact ]
127
164
public async Task PersistStateAsync_ContinuesInvokingPauseCallbacksDuringPersistIfACallbackThrows ( )
128
165
{
129
166
// Arrange
130
167
var sink = new TestSink ( ) ;
131
168
var loggerFactory = new TestLoggerFactory ( sink , true ) ;
132
- var logger = loggerFactory . CreateLogger < ComponentApplicationLifetime > ( ) ;
133
- var state = new Dictionary < string , byte [ ] > ( ) ;
169
+ var logger = loggerFactory . CreateLogger < ComponentStatePersistenceManager > ( ) ;
170
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
134
171
var store = new TestStore ( state ) ;
135
- var lifetime = new ComponentApplicationLifetime ( logger ) ;
172
+ var lifetime = new ComponentStatePersistenceManager ( logger ) ;
136
173
var renderer = new TestRenderer ( ) ;
137
174
var data = new byte [ ] { 1 , 2 , 3 , 4 } ;
138
175
var invoked = false ;
139
176
140
- lifetime . State . OnPersisting += ( ) => throw new InvalidOperationException ( ) ;
141
- lifetime . State . OnPersisting += ( ) => { invoked = true ; return Task . CompletedTask ; } ;
177
+ lifetime . State . RegisterOnPersisting ( ( ) => throw new InvalidOperationException ( ) ) ;
178
+ lifetime . State . RegisterOnPersisting ( ( ) => { invoked = true ; return Task . CompletedTask ; } ) ;
142
179
143
180
// Act
144
181
await lifetime . PersistStateAsync ( store , renderer ) ;
@@ -155,16 +192,16 @@ public async Task PersistStateAsync_ContinuesInvokingPauseCallbacksDuringPersist
155
192
// Arrange
156
193
var sink = new TestSink ( ) ;
157
194
var loggerFactory = new TestLoggerFactory ( sink , true ) ;
158
- var logger = loggerFactory . CreateLogger < ComponentApplicationLifetime > ( ) ;
159
- var state = new Dictionary < string , byte [ ] > ( ) ;
195
+ var logger = loggerFactory . CreateLogger < ComponentStatePersistenceManager > ( ) ;
196
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
160
197
var store = new TestStore ( state ) ;
161
- var lifetime = new ComponentApplicationLifetime ( logger ) ;
198
+ var lifetime = new ComponentStatePersistenceManager ( logger ) ;
162
199
var renderer = new TestRenderer ( ) ;
163
200
var invoked = false ;
164
201
var tcs = new TaskCompletionSource ( ) ;
165
202
166
- lifetime . State . OnPersisting += async ( ) => { await tcs . Task ; throw new InvalidOperationException ( ) ; } ;
167
- lifetime . State . OnPersisting += ( ) => { invoked = true ; return Task . CompletedTask ; } ;
203
+ lifetime . State . RegisterOnPersisting ( async ( ) => { await tcs . Task ; throw new InvalidOperationException ( ) ; } ) ;
204
+ lifetime . State . RegisterOnPersisting ( ( ) => { invoked = true ; return Task . CompletedTask ; } ) ;
168
205
169
206
// Act
170
207
var persistTask = lifetime . PersistStateAsync ( store , renderer ) ;
@@ -182,14 +219,18 @@ public async Task PersistStateAsync_ContinuesInvokingPauseCallbacksDuringPersist
182
219
public async Task PersistStateAsync_ThrowsWhenDeveloperTriesToPersistStateMultipleTimes ( )
183
220
{
184
221
// Arrange
185
- var state = new Dictionary < string , byte [ ] > ( ) ;
222
+ var state = new Dictionary < string , ReadOnlySequence < byte > > ( ) ;
186
223
var store = new TestStore ( state ) ;
187
- var lifetime = new ComponentApplicationLifetime ( NullLogger < ComponentApplicationLifetime > . Instance ) ;
224
+ var lifetime = new ComponentStatePersistenceManager ( NullLogger < ComponentStatePersistenceManager > . Instance ) ;
188
225
189
226
var renderer = new TestRenderer ( ) ;
190
227
var data = new byte [ ] { 1 , 2 , 3 , 4 } ;
191
228
192
- lifetime . State . PersistState ( "MyState" , new byte [ ] { 1 , 2 , 3 , 4 } ) ;
229
+ lifetime . State . RegisterOnPersisting ( ( ) =>
230
+ {
231
+ lifetime . State . Persist ( "MyState" , writer => writer . Write ( new byte [ ] { 1 , 2 , 3 , 4 } ) ) ;
232
+ return Task . CompletedTask ;
233
+ } ) ;
193
234
194
235
// Act
195
236
await lifetime . PersistStateAsync ( store , renderer ) ;
@@ -219,23 +260,24 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
219
260
}
220
261
}
221
262
222
- private class TestStore : IComponentApplicationStateStore
263
+ private class TestStore : IPersistentComponentStateStore
223
264
{
224
- public TestStore ( IDictionary < string , byte [ ] > initialState )
265
+ public TestStore ( IDictionary < string , ReadOnlySequence < byte > > initialState )
225
266
{
226
267
State = initialState ;
227
268
}
228
269
229
- public IDictionary < string , byte [ ] > State { get ; set ; }
270
+ public IDictionary < string , ReadOnlySequence < byte > > State { get ; set ; }
230
271
231
- public Task < IDictionary < string , byte [ ] > > GetPersistedStateAsync ( )
272
+ public Task < IDictionary < string , ReadOnlySequence < byte > > > GetPersistedStateAsync ( )
232
273
{
233
274
return Task . FromResult ( State ) ;
234
275
}
235
276
236
- public Task PersistStateAsync ( IReadOnlyDictionary < string , byte [ ] > state )
277
+ public Task PersistStateAsync ( IReadOnlyDictionary < string , ReadOnlySequence < byte > > state )
237
278
{
238
- State = new Dictionary < string , byte [ ] > ( state ) ;
279
+ // We copy the data here because it's no longer available after this call completes.
280
+ State = state . ToDictionary ( kvp => kvp . Key , kvp => new ReadOnlySequence < byte > ( kvp . Value . ToArray ( ) ) ) ;
239
281
return Task . CompletedTask ;
240
282
}
241
283
}
0 commit comments