2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Text . Encodings . Web ;
5
- using Microsoft . AspNetCore . Components ;
6
5
using Microsoft . AspNetCore . Components . Authorization ;
7
6
using Microsoft . AspNetCore . Components . Infrastructure ;
8
7
using Microsoft . AspNetCore . Components . Routing ;
9
8
using Microsoft . AspNetCore . Components . Web ;
9
+ using Microsoft . AspNetCore . DataProtection ;
10
10
using Microsoft . AspNetCore . Html ;
11
11
using Microsoft . AspNetCore . Http ;
12
12
using Microsoft . AspNetCore . Http . Extensions ;
13
- using Microsoft . AspNetCore . Mvc . Rendering ;
14
13
using Microsoft . Extensions . DependencyInjection ;
15
14
16
- namespace Microsoft . AspNetCore . Mvc . ViewFeatures ;
15
+ namespace Microsoft . AspNetCore . Components . Endpoints ;
17
16
18
17
// Wraps the public HtmlRenderer APIs so that the output also gets annotated with prerendering markers.
19
18
// This allows the prerendered content to switch later into interactive mode.
20
19
// This class also deals with initializing the standard component DI services once per request.
21
- internal sealed class ComponentPrerenderer
20
+ internal sealed class ComponentPrerenderer : IComponentPrerenderer
22
21
{
23
22
private static readonly object ComponentSequenceKey = new object ( ) ;
24
23
private static readonly object InvokedRenderModesKey = new object ( ) ;
25
24
26
25
private readonly HtmlRenderer _htmlRenderer ;
27
- private readonly ServerComponentSerializer _serverComponentSerializer ;
26
+ private readonly IServiceProvider _services ;
28
27
private readonly object _servicesInitializedLock = new ( ) ;
29
- private Task _servicesInitializedTask ;
28
+ private Task ? _servicesInitializedTask ;
30
29
31
30
public ComponentPrerenderer (
32
31
HtmlRenderer htmlRenderer ,
33
- ServerComponentSerializer serverComponentSerializer )
32
+ IServiceProvider services )
34
33
{
35
- _serverComponentSerializer = serverComponentSerializer ;
36
34
_htmlRenderer = htmlRenderer ;
35
+ _services = services ;
37
36
}
38
37
39
38
public Dispatcher Dispatcher => _htmlRenderer . Dispatcher ;
@@ -72,6 +71,46 @@ public async ValueTask<IHtmlAsyncContent> PrerenderComponentAsync(
72
71
} ;
73
72
}
74
73
74
+ public async ValueTask < IHtmlContent > PrerenderPersistedStateAsync ( HttpContext httpContext , PersistedStateSerializationMode serializationMode )
75
+ {
76
+ // First we resolve "infer" mode to a specific mode
77
+ if ( serializationMode == PersistedStateSerializationMode . Infer )
78
+ {
79
+ switch ( GetPersistStateRenderMode ( httpContext ) )
80
+ {
81
+ case InvokedRenderModes . Mode . None :
82
+ return ComponentStateHtmlContent . Empty ;
83
+ case InvokedRenderModes . Mode . ServerAndWebAssembly :
84
+ throw new InvalidOperationException (
85
+ Resources . FailedToInferComponentPersistenceMode ) ;
86
+ case InvokedRenderModes . Mode . Server :
87
+ serializationMode = PersistedStateSerializationMode . Server ;
88
+ break ;
89
+ case InvokedRenderModes . Mode . WebAssembly :
90
+ serializationMode = PersistedStateSerializationMode . WebAssembly ;
91
+ break ;
92
+ default :
93
+ throw new InvalidOperationException ( "Invalid persistence mode" ) ;
94
+ }
95
+ }
96
+
97
+ // Now given the mode, we obtain a particular store for that mode
98
+ var store = serializationMode switch
99
+ {
100
+ PersistedStateSerializationMode . Server =>
101
+ new ProtectedPrerenderComponentApplicationStore ( httpContext . RequestServices . GetRequiredService < IDataProtectionProvider > ( ) ) ,
102
+ PersistedStateSerializationMode . WebAssembly =>
103
+ new PrerenderComponentApplicationStore ( ) ,
104
+ _ =>
105
+ throw new InvalidOperationException ( "Invalid persistence mode." )
106
+ } ;
107
+
108
+ // Finally, persist the state and return the HTML content
109
+ var manager = httpContext . RequestServices . GetRequiredService < ComponentStatePersistenceManager > ( ) ;
110
+ await manager . PersistStateAsync ( store , _htmlRenderer . Dispatcher ) ;
111
+ return new ComponentStateHtmlContent ( store ) ;
112
+ }
113
+
75
114
private async ValueTask < HtmlComponent > PrerenderComponentCoreAsync (
76
115
ParameterView parameters ,
77
116
HttpContext httpContext ,
@@ -108,7 +147,7 @@ private static ServerComponentInvocationSequence GetOrCreateInvocationId(HttpCon
108
147
httpContext . Items [ ComponentSequenceKey ] = result ;
109
148
}
110
149
111
- return ( ServerComponentInvocationSequence ) result ;
150
+ return ( ServerComponentInvocationSequence ) result ! ;
112
151
}
113
152
114
153
// Internal for test only
@@ -131,7 +170,7 @@ internal static void UpdateSaveStateRenderMode(HttpContext httpContext, RenderMo
131
170
InvokedRenderModes . Mode . Server :
132
171
InvokedRenderModes . Mode . WebAssembly ;
133
172
134
- var invokedMode = ( InvokedRenderModes ) result ;
173
+ var invokedMode = ( InvokedRenderModes ) result ! ;
135
174
if ( invokedMode . Value != currentInvocation )
136
175
{
137
176
invokedMode . Value = InvokedRenderModes . Mode . ServerAndWebAssembly ;
@@ -142,14 +181,9 @@ internal static void UpdateSaveStateRenderMode(HttpContext httpContext, RenderMo
142
181
143
182
internal static InvokedRenderModes . Mode GetPersistStateRenderMode ( HttpContext httpContext )
144
183
{
145
- if ( httpContext . Items . TryGetValue ( InvokedRenderModesKey , out var result ) )
146
- {
147
- return ( ( InvokedRenderModes ) result ) . Value ;
148
- }
149
- else
150
- {
151
- return InvokedRenderModes . Mode . None ;
152
- }
184
+ return httpContext . Items . TryGetValue ( InvokedRenderModesKey , out var result )
185
+ ? ( ( InvokedRenderModes ) result ! ) . Value
186
+ : InvokedRenderModes . Mode . None ;
153
187
}
154
188
155
189
private async ValueTask < IHtmlAsyncContent > StaticComponentAsync ( HttpContext context , Type type , ParameterView parametersCollection )
@@ -168,7 +202,11 @@ private async Task<IHtmlAsyncContent> PrerenderedServerComponentAsync(HttpContex
168
202
context . Response . Headers . CacheControl = "no-cache, no-store, max-age=0" ;
169
203
}
170
204
171
- var marker = _serverComponentSerializer . SerializeInvocation (
205
+ // Lazy because we don't actually want to require a whole chain of services including Data Protection
206
+ // to be required unless you actually use Server render mode.
207
+ var serverComponentSerializer = _services . GetRequiredService < ServerComponentSerializer > ( ) ;
208
+
209
+ var marker = serverComponentSerializer . SerializeInvocation (
172
210
invocationId ,
173
211
type ,
174
212
parametersCollection ,
@@ -204,7 +242,11 @@ private IHtmlAsyncContent NonPrerenderedServerComponent(HttpContext context, Ser
204
242
context . Response . Headers . CacheControl = "no-cache, no-store, max-age=0" ;
205
243
}
206
244
207
- var marker = _serverComponentSerializer . SerializeInvocation ( invocationId , type , parametersCollection , prerendered : false ) ;
245
+ // Lazy because we don't actually want to require a whole chain of services including Data Protection
246
+ // to be required unless you actually use Server render mode.
247
+ var serverComponentSerializer = _services . GetRequiredService < ServerComponentSerializer > ( ) ;
248
+
249
+ var marker = serverComponentSerializer . SerializeInvocation ( invocationId , type , parametersCollection , prerendered : false ) ;
208
250
return new PrerenderedComponentHtmlContent ( null , null , marker , null ) ;
209
251
}
210
252
@@ -216,16 +258,16 @@ private static IHtmlAsyncContent NonPrerenderedWebAssemblyComponent(Type type, P
216
258
217
259
// An implementation of IHtmlContent that holds a reference to a component until we're ready to emit it as HTML to the response.
218
260
// We don't construct the actual HTML until we receive the call to WriteTo.
219
- private class PrerenderedComponentHtmlContent : IHtmlContent , IHtmlAsyncContent
261
+ private class PrerenderedComponentHtmlContent : IHtmlAsyncContent
220
262
{
221
- private readonly Dispatcher _dispatcher ;
222
- private readonly HtmlComponent _htmlToEmitOrNull ;
263
+ private readonly Dispatcher ? _dispatcher ;
264
+ private readonly HtmlComponent ? _htmlToEmitOrNull ;
223
265
private readonly ServerComponentMarker ? _serverMarker ;
224
266
private readonly WebAssemblyComponentMarker ? _webAssemblyMarker ;
225
267
226
268
public PrerenderedComponentHtmlContent (
227
- Dispatcher dispatcher ,
228
- HtmlComponent htmlToEmitOrNull , // If null, we're only emitting the markers
269
+ Dispatcher ? dispatcher , // If null, we're only emitting the markers
270
+ HtmlComponent ? htmlToEmitOrNull , // If null, we're only emitting the markers
229
271
ServerComponentMarker ? serverMarker ,
230
272
WebAssemblyComponentMarker ? webAssemblyMarker )
231
273
{
@@ -235,9 +277,18 @@ public PrerenderedComponentHtmlContent(
235
277
_webAssemblyMarker = webAssemblyMarker ;
236
278
}
237
279
238
- // For back-compat, we have to supply an implemention of IHtmlContent. However this will only work
239
- // if you call it on the HtmlRenderer's sync context. The framework itself will not call this directly
240
- // and will instead use WriteToAsync which deals with dispatching to the sync context.
280
+ public async ValueTask WriteToAsync ( TextWriter writer )
281
+ {
282
+ if ( _dispatcher is null )
283
+ {
284
+ WriteTo ( writer , HtmlEncoder . Default ) ;
285
+ }
286
+ else
287
+ {
288
+ await _dispatcher . InvokeAsync ( ( ) => WriteTo ( writer , HtmlEncoder . Default ) ) ;
289
+ }
290
+ }
291
+
241
292
public void WriteTo ( TextWriter writer , HtmlEncoder encoder )
242
293
{
243
294
if ( _serverMarker . HasValue )
@@ -263,18 +314,6 @@ public void WriteTo(TextWriter writer, HtmlEncoder encoder)
263
314
}
264
315
}
265
316
}
266
-
267
- public async ValueTask WriteToAsync ( TextWriter writer )
268
- {
269
- if ( _dispatcher is null )
270
- {
271
- WriteTo ( writer , HtmlEncoder . Default ) ;
272
- }
273
- else
274
- {
275
- await _dispatcher . InvokeAsync ( ( ) => WriteTo ( writer , HtmlEncoder . Default ) ) ;
276
- }
277
- }
278
317
}
279
318
280
319
private static async Task InitializeStandardComponentServicesAsync ( HttpContext httpContext )
@@ -313,4 +352,28 @@ private static string GetContextBaseUri(HttpRequest request)
313
352
// it has to end with a trailing slash
314
353
return result . EndsWith ( '/' ) ? result : result += "/" ;
315
354
}
355
+
356
+ private sealed class ComponentStateHtmlContent : IHtmlContent
357
+ {
358
+ private PrerenderComponentApplicationStore ? _store ;
359
+
360
+ public static ComponentStateHtmlContent Empty { get ; }
361
+ = new ComponentStateHtmlContent ( null ) ;
362
+
363
+ public ComponentStateHtmlContent ( PrerenderComponentApplicationStore ? store )
364
+ {
365
+ _store = store ;
366
+ }
367
+
368
+ public void WriteTo ( TextWriter writer , HtmlEncoder encoder )
369
+ {
370
+ if ( _store != null )
371
+ {
372
+ writer . Write ( "<!--Blazor-Component-State:" ) ;
373
+ writer . Write ( _store . PersistedState ) ;
374
+ writer . Write ( "-->" ) ;
375
+ _store = null ;
376
+ }
377
+ }
378
+ }
316
379
}
0 commit comments