@@ -25,7 +25,16 @@ await circuit.Renderer.Dispatcher.InvokeAsync(async () =>
2525 {
2626 var renderer = circuit . Renderer ;
2727 var persistenceManager = circuit . Services . GetRequiredService < ComponentStatePersistenceManager > ( ) ;
28- var collector = new CircuitPersistenceManagerCollector ( circuitOptions , serverComponentSerializer , circuit . Renderer ) ;
28+
29+ // TODO (OR): Select solution variant
30+ // Variant B: Client-side check
31+ var distributedRetention = circuitOptions . Value . PersistedCircuitDistributedRetentionPeriod ;
32+ var localRetention = circuitOptions . Value . PersistedCircuitInMemoryRetentionPeriod ;
33+ var maxRetention = ( distributedRetention > localRetention ? distributedRetention : localRetention ) ?? ServerComponentSerializationSettings . DataExpiration ;
34+ var expiration = DateTimeOffset . UtcNow . Add ( maxRetention ) ;
35+
36+ var collector = new CircuitPersistenceManagerCollector ( serverComponentSerializer , circuit . Renderer , maxRetention ) ;
37+
2938 using var subscription = persistenceManager . State . RegisterOnPersisting (
3039 collector . PersistRootComponents ,
3140 RenderMode . InteractiveServer ) ;
@@ -34,7 +43,7 @@ await circuit.Renderer.Dispatcher.InvokeAsync(async () =>
3443
3544 if ( saveStateToClient )
3645 {
37- await SaveStateToClient ( circuit , collector . PersistedCircuitState , cancellation ) ;
46+ await SaveStateToClient ( circuit , collector . PersistedCircuitState , expiration , cancellation ) ;
3847 }
3948 else
4049 {
@@ -46,10 +55,10 @@ await circuitPersistenceProvider.PersistCircuitAsync(
4655 } ) ;
4756 }
4857
49- internal async Task SaveStateToClient ( CircuitHost circuit , PersistedCircuitState state , CancellationToken cancellation = default )
58+ internal async Task SaveStateToClient ( CircuitHost circuit , PersistedCircuitState state , DateTimeOffset expiration , CancellationToken cancellation = default )
5059 {
5160 var ( rootComponents , applicationState ) = await ToProtectedStateAsync ( state ) ;
52- if ( ! await circuit . SendPersistedStateToClient ( rootComponents , applicationState , cancellation ) )
61+ if ( ! await circuit . SendPersistedStateToClient ( rootComponents , applicationState , expiration , cancellation ) )
5362 {
5463 try
5564 {
@@ -101,6 +110,27 @@ public async Task<PersistedCircuitState> ResumeCircuitAsync(CircuitId circuitId,
101110 return await circuitPersistenceProvider . RestoreCircuitAsync ( circuitId , cancellation ) ;
102111 }
103112
113+ internal static bool CheckRootComponentMarkers ( IServerComponentDeserializer serverComponentDeserializer , byte [ ] rootComponents )
114+ {
115+ var persistedMarkers = TryDeserializeMarkers ( rootComponents ) ;
116+
117+ if ( persistedMarkers == null )
118+ {
119+ return false ;
120+ }
121+
122+ foreach ( var marker in persistedMarkers )
123+ {
124+ if ( ! serverComponentDeserializer . TryDeserializeWebRootComponentDescriptor ( marker . Value , out var _ ) )
125+ {
126+ // OR: Expired state
127+ return false ;
128+ }
129+ }
130+
131+ return true ;
132+ }
133+
104134 // We are going to construct a RootComponentOperationBatch but we are going to replace the descriptors from the client with the
105135 // descriptors that we have persisted when pausing the circuit.
106136 // The way pausing and resuming works is that when the client starts the resume process, it 'simulates' that an SSR has happened and
@@ -152,55 +182,63 @@ internal static RootComponentOperationBatch ToRootComponentOperationBatch(
152182
153183 if ( ! serverComponentDeserializer . TryDeserializeWebRootComponentDescriptor ( operation . Marker . Value , out var descriptor ) )
154184 {
185+ // OR: Expired state
155186 return null ;
156187 }
157188
158189 operation . Descriptor = descriptor ;
159190 }
160191
161192 return batch ;
193+ }
162194
163- static Dictionary < int , ComponentMarker > TryDeserializeMarkers ( byte [ ] rootComponents )
195+ private static Dictionary < int , ComponentMarker > TryDeserializeMarkers ( byte [ ] rootComponents )
196+ {
197+ if ( rootComponents == null || rootComponents . Length == 0 )
164198 {
165- if ( rootComponents == null || rootComponents . Length == 0 )
166- {
167- return null ;
168- }
199+ return null ;
200+ }
169201
170- try
171- {
172- return JsonSerializer . Deserialize < Dictionary < int , ComponentMarker > > (
173- rootComponents ,
174- JsonSerializerOptionsProvider . Options ) ;
175- }
176- catch
177- {
178- return null ;
179- }
202+ try
203+ {
204+ return JsonSerializer . Deserialize < Dictionary < int , ComponentMarker > > (
205+ rootComponents ,
206+ JsonSerializerOptionsProvider . Options ) ;
207+ }
208+ catch
209+ {
210+ return null ;
180211 }
181212 }
182213
183- private class CircuitPersistenceManagerCollector (
184- IOptions < CircuitOptions > circuitOptions ,
185- ServerComponentSerializer serverComponentSerializer ,
186- RemoteRenderer renderer )
187- : IPersistentComponentStateStore
214+ private class CircuitPersistenceManagerCollector : IPersistentComponentStateStore
188215 {
216+ private readonly ServerComponentSerializer _serverComponentSerializer ;
217+ private readonly RemoteRenderer _renderer ;
218+ private readonly TimeSpan _maxRetention ;
219+
220+ public CircuitPersistenceManagerCollector (
221+ ServerComponentSerializer serverComponentSerializer ,
222+ RemoteRenderer renderer ,
223+ TimeSpan maxRetention )
224+ {
225+ _serverComponentSerializer = serverComponentSerializer ;
226+ _renderer = renderer ;
227+ _maxRetention = maxRetention ;
228+ }
229+
189230 internal PersistedCircuitState PersistedCircuitState { get ; private set ; }
190231
191232 public Task PersistRootComponents ( )
192233 {
193234 var persistedComponents = new Dictionary < int , ComponentMarker > ( ) ;
194- var components = renderer . GetOrCreateWebRootComponentManager ( ) . GetRootComponents ( ) ;
235+ var components = _renderer . GetOrCreateWebRootComponentManager ( ) . GetRootComponents ( ) ;
195236 var invocation = new ServerComponentInvocationSequence ( ) ;
237+
196238 foreach ( var ( id , componentKey , ( componentType , parameters ) ) in components )
197239 {
198- var distributedRetention = circuitOptions . Value . PersistedCircuitInMemoryRetentionPeriod ;
199- var localRetention = circuitOptions . Value . PersistedCircuitInMemoryRetentionPeriod ;
200- var maxRetention = distributedRetention > localRetention ? distributedRetention : localRetention ;
201-
202240 var marker = ComponentMarker . Create ( ComponentMarker . ServerMarkerType , prerendered : false , componentKey ) ;
203- serverComponentSerializer . SerializeInvocation ( ref marker , invocation , componentType , parameters , maxRetention ) ;
241+ _serverComponentSerializer . SerializeInvocation ( ref marker , invocation , componentType , parameters , _maxRetention ) ;
204242 persistedComponents . Add ( id , marker ) ;
205243 }
206244
0 commit comments