1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
- using System ;
5
- using System . Collections . Generic ;
4
+ using System . Buffers ;
6
5
using System . Diagnostics . CodeAnalysis ;
7
6
using System . Text . Json ;
8
- using System . Threading . Tasks ;
7
+ using Microsoft . AspNetCore . Components . Infrastructure ;
9
8
using static Microsoft . AspNetCore . Internal . LinkerFlags ;
10
9
11
10
namespace Microsoft . AspNetCore . Components
12
11
{
13
12
/// <summary>
14
13
/// The state for the components and services of a components application.
15
14
/// </summary>
16
- public class ComponentApplicationState
15
+ public class PersistentComponentState
17
16
{
18
- private IDictionary < string , byte [ ] > ? _existingState ;
19
- private readonly IDictionary < string , byte [ ] > _currentState ;
20
- private readonly List < OnPersistingCallback > _registeredCallbacks ;
17
+ private IDictionary < string , ReadOnlySequence < byte > > ? _existingState ;
18
+ private readonly IDictionary < string , PooledByteBufferWriter > _currentState ;
21
19
22
- internal ComponentApplicationState (
23
- IDictionary < string , byte [ ] > currentState ,
24
- List < OnPersistingCallback > pauseCallbacks )
20
+ private readonly List < Func < Task > > _registeredCallbacks ;
21
+
22
+ internal PersistentComponentState (
23
+ IDictionary < string , PooledByteBufferWriter > currentState ,
24
+ List < Func < Task > > pauseCallbacks )
25
25
{
26
26
_currentState = currentState ;
27
27
_registeredCallbacks = pauseCallbacks ;
28
28
}
29
29
30
- internal void InitializeExistingState ( IDictionary < string , byte [ ] > existingState )
30
+ internal bool PersistingState { get ; set ; }
31
+
32
+ internal void InitializeExistingState ( IDictionary < string , ReadOnlySequence < byte > > existingState )
31
33
{
32
34
if ( _existingState != null )
33
35
{
34
- throw new InvalidOperationException ( "ComponentApplicationState already initialized." ) ;
36
+ throw new InvalidOperationException ( "PersistentComponentState already initialized." ) ;
35
37
}
36
38
_existingState = existingState ?? throw new ArgumentNullException ( nameof ( existingState ) ) ;
37
39
}
38
40
39
41
/// <summary>
40
- /// Represents the method that performs operations when <see cref="OnPersisting"/> is raised and the application is about to be paused.
42
+ /// Register a callback to persist the component state when the application is about to be paused.
43
+ /// Registered callbacks can use this opportunity to persist their state so that it can be retrieved when the application resumes.
41
44
/// </summary>
42
- /// <returns>A <see cref="Task"/> that will complete when the method is done preparing for the application pause.</returns>
43
- public delegate Task OnPersistingCallback ( ) ;
44
-
45
- /// <summary>
46
- /// An event that is raised when the application is about to be paused.
47
- /// Registered handlers can use this opportunity to persist their state so that it can be retrieved when the application resumes.
48
- /// </summary>
49
- public event OnPersistingCallback OnPersisting
45
+ /// <param name="callback">The callback to invoke when the application is being paused.</param>
46
+ /// <returns>A subscription that can be used to unregister the callback when disposed.</returns>
47
+ public PersistingComponentStateSubscription RegisterOnPersisting ( Func < Task > callback )
50
48
{
51
- add
49
+ if ( callback == null )
52
50
{
53
- if ( value == null )
54
- {
55
- throw new ArgumentNullException ( nameof ( value ) ) ;
56
- }
57
-
58
- _registeredCallbacks . Add ( value ) ;
51
+ throw new ArgumentNullException ( nameof ( callback ) ) ;
59
52
}
60
- remove
61
- {
62
- if ( value == null )
63
- {
64
- throw new ArgumentNullException ( nameof ( value ) ) ;
65
- }
66
53
67
- _registeredCallbacks . Remove ( value ) ;
68
- }
54
+ _registeredCallbacks . Add ( callback ) ;
55
+
56
+ return new PersistingComponentStateSubscription ( _registeredCallbacks , callback ) ;
69
57
}
70
58
71
59
/// <summary>
72
60
/// Tries to retrieve the persisted state with the given <paramref name="key"/>.
73
61
/// When the key is present, the state is successfully returned via <paramref name="value"/>
74
- /// and removed from the <see cref="ComponentApplicationState "/>.
62
+ /// and removed from the <see cref="PersistentComponentState "/>.
75
63
/// </summary>
76
64
/// <param name="key">The key used to persist the state.</param>
77
65
/// <param name="value">The persisted state.</param>
78
66
/// <returns><c>true</c> if the state was found; <c>false</c> otherwise.</returns>
79
- public bool TryTakePersistedState ( string key , [ MaybeNullWhen ( false ) ] out byte [ ] ? value )
67
+ public bool TryTake ( string key , out ReadOnlySequence < byte > value )
80
68
{
81
69
if ( key is null )
82
70
{
@@ -89,7 +77,7 @@ public bool TryTakePersistedState(string key, [MaybeNullWhen(false)] out byte[]?
89
77
// and we don't want to fail in that case.
90
78
// When a service is prerendering there is no state to restore and in other cases the host
91
79
// is responsible for initializing the state before services or components can access it.
92
- value = null ;
80
+ value = default ;
93
81
return false ;
94
82
}
95
83
@@ -105,27 +93,35 @@ public bool TryTakePersistedState(string key, [MaybeNullWhen(false)] out byte[]?
105
93
}
106
94
107
95
/// <summary>
108
- /// Persists the serialized state <paramref name="value "/> for the given <paramref name="key"/>.
96
+ /// Persists the serialized state <paramref name="valueWriter "/> for the given <paramref name="key"/>.
109
97
/// </summary>
110
98
/// <param name="key">The key to use to persist the state.</param>
111
- /// <param name="value ">The state to persist.</param>
112
- public void PersistState ( string key , byte [ ] value )
99
+ /// <param name="valueWriter ">The state to persist.</param>
100
+ public void Persist ( string key , Action < IBufferWriter < byte > > valueWriter )
113
101
{
114
102
if ( key is null )
115
103
{
116
104
throw new ArgumentNullException ( nameof ( key ) ) ;
117
105
}
118
106
119
- if ( value is null )
107
+ if ( valueWriter is null )
108
+ {
109
+ throw new ArgumentNullException ( nameof ( valueWriter ) ) ;
110
+ }
111
+
112
+ if ( ! PersistingState )
120
113
{
121
- throw new ArgumentNullException ( nameof ( value ) ) ;
114
+ throw new InvalidOperationException ( "Persisting state is only allowed during an OnPersisting callback." ) ;
122
115
}
123
116
124
117
if ( _currentState . ContainsKey ( key ) )
125
118
{
126
119
throw new ArgumentException ( $ "There is already a persisted object under the same key '{ key } '") ;
127
120
}
128
- _currentState . Add ( key , value ) ;
121
+
122
+ var writer = new PooledByteBufferWriter ( ) ;
123
+ _currentState . Add ( key , writer ) ;
124
+ valueWriter ( writer ) ;
129
125
}
130
126
131
127
/// <summary>
@@ -142,29 +138,47 @@ public void PersistState(string key, byte[] value)
142
138
throw new ArgumentNullException ( nameof ( key ) ) ;
143
139
}
144
140
145
- PersistState ( key , JsonSerializer . SerializeToUtf8Bytes ( instance , JsonSerializerOptionsProvider . Options ) ) ;
141
+ if ( key is null )
142
+ {
143
+ throw new ArgumentNullException ( nameof ( key ) ) ;
144
+ }
145
+
146
+ if ( ! PersistingState )
147
+ {
148
+ throw new InvalidOperationException ( "Persisting state is only allowed during an OnPersisting callback." ) ;
149
+ }
150
+
151
+ if ( _currentState . ContainsKey ( key ) )
152
+ {
153
+ throw new ArgumentException ( $ "There is already a persisted object under the same key '{ key } '") ;
154
+ }
155
+
156
+ var writer = new PooledByteBufferWriter ( ) ;
157
+ _currentState . Add ( key , writer ) ;
158
+ JsonSerializer . Serialize ( new Utf8JsonWriter ( writer ) , instance , JsonSerializerOptionsProvider . Options ) ;
146
159
}
147
160
148
161
/// <summary>
149
162
/// Tries to retrieve the persisted state as JSON with the given <paramref name="key"/> and deserializes it into an
150
163
/// instance of type <typeparamref name="TValue"/>.
151
164
/// When the key is present, the state is successfully returned via <paramref name="instance"/>
152
- /// and removed from the <see cref="ComponentApplicationState "/>.
165
+ /// and removed from the <see cref="PersistentComponentState "/>.
153
166
/// </summary>
154
167
/// <param name="key">The key used to persist the instance.</param>
155
168
/// <param name="instance">The persisted instance.</param>
156
169
/// <returns><c>true</c> if the state was found; <c>false</c> otherwise.</returns>
157
170
[ RequiresUnreferencedCode ( "JSON serialization and deserialization might require types that cannot be statically analyzed." ) ]
158
- public bool TryTakeAsJson < [ DynamicallyAccessedMembers ( JsonSerialized ) ] TValue > ( string key , [ MaybeNullWhen ( false ) ] out TValue ? instance )
171
+ public bool TryTakeFromJson < [ DynamicallyAccessedMembers ( JsonSerialized ) ] TValue > ( string key , [ MaybeNullWhen ( false ) ] out TValue ? instance )
159
172
{
160
173
if ( key is null )
161
174
{
162
175
throw new ArgumentNullException ( nameof ( key ) ) ;
163
176
}
164
177
165
- if ( TryTakePersistedState ( key , out var data ) )
178
+ if ( TryTake ( key , out var data ) )
166
179
{
167
- instance = JsonSerializer . Deserialize < TValue > ( data , JsonSerializerOptionsProvider . Options ) ! ;
180
+ var reader = new Utf8JsonReader ( data ) ;
181
+ instance = JsonSerializer . Deserialize < TValue > ( ref reader , JsonSerializerOptionsProvider . Options ) ! ;
168
182
return true ;
169
183
}
170
184
else
0 commit comments