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 . Diagnostics . CodeAnalysis ;
4
5
using System . Text . Encodings . Web ;
5
- using Microsoft . AspNetCore . Components . Authorization ;
6
- using Microsoft . AspNetCore . Components . Infrastructure ;
7
- using Microsoft . AspNetCore . Components . Routing ;
8
6
using Microsoft . AspNetCore . Components . Web ;
9
- using Microsoft . AspNetCore . DataProtection ;
10
7
using Microsoft . AspNetCore . Html ;
11
8
using Microsoft . AspNetCore . Http ;
12
- using Microsoft . AspNetCore . Http . Extensions ;
13
9
using Microsoft . Extensions . DependencyInjection ;
14
10
15
11
namespace Microsoft . AspNetCore . Components . Endpoints ;
16
12
17
- // Wraps the public HtmlRenderer APIs so that the output also gets annotated with prerendering markers.
18
- // This allows the prerendered content to switch later into interactive mode.
19
- // This class also deals with initializing the standard component DI services once per request.
20
- internal sealed class ComponentPrerenderer : IComponentPrerenderer
13
+ internal sealed partial class EndpointHtmlRenderer
21
14
{
22
15
private static readonly object ComponentSequenceKey = new object ( ) ;
23
- private static readonly object InvokedRenderModesKey = new object ( ) ;
24
-
25
- private readonly HtmlRenderer _htmlRenderer ;
26
- private readonly IServiceProvider _services ;
27
- private readonly object _servicesInitializedLock = new ( ) ;
28
- private Task ? _servicesInitializedTask ;
29
-
30
- public ComponentPrerenderer (
31
- HtmlRenderer htmlRenderer ,
32
- IServiceProvider services )
33
- {
34
- _htmlRenderer = htmlRenderer ;
35
- _services = services ;
36
- }
37
-
38
- public Dispatcher Dispatcher => _htmlRenderer . Dispatcher ;
39
16
40
17
public async ValueTask < IHtmlAsyncContent > PrerenderComponentAsync (
41
18
HttpContext httpContext ,
@@ -52,10 +29,8 @@ public async ValueTask<IHtmlAsyncContent> PrerenderComponentAsync(
52
29
}
53
30
54
31
// Make sure we only initialize the services once, but on every call we wait for that process to complete
55
- lock ( _servicesInitializedLock )
56
- {
57
- _servicesInitializedTask ??= InitializeStandardComponentServicesAsync ( httpContext ) ;
58
- }
32
+ // This does not have to be threadsafe since it's not valid to call this simultaneously from multiple threads.
33
+ _servicesInitializedTask ??= InitializeStandardComponentServicesAsync ( httpContext ) ;
59
34
await _servicesInitializedTask ;
60
35
61
36
UpdateSaveStateRenderMode ( httpContext , prerenderMode ) ;
@@ -71,55 +46,19 @@ public async ValueTask<IHtmlAsyncContent> PrerenderComponentAsync(
71
46
} ;
72
47
}
73
48
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
-
114
49
private async ValueTask < HtmlComponent > PrerenderComponentCoreAsync (
115
50
ParameterView parameters ,
116
51
HttpContext httpContext ,
117
- Type componentType )
52
+ [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . All ) ] Type componentType )
118
53
{
119
54
try
120
55
{
121
- return await _htmlRenderer . Dispatcher . InvokeAsync ( ( ) =>
122
- _htmlRenderer . RenderComponentAsync ( componentType , parameters ) ) ;
56
+ return await Dispatcher . InvokeAsync ( async ( ) =>
57
+ {
58
+ var content = BeginRenderingComponent ( componentType , parameters ) ;
59
+ await content . WaitForQuiescenceAsync ( ) ;
60
+ return content ;
61
+ } ) ;
123
62
}
124
63
catch ( NavigationException navigationException )
125
64
{
@@ -150,49 +89,13 @@ private static ServerComponentInvocationSequence GetOrCreateInvocationId(HttpCon
150
89
return ( ServerComponentInvocationSequence ) result ! ;
151
90
}
152
91
153
- // Internal for test only
154
- internal static void UpdateSaveStateRenderMode ( HttpContext httpContext , RenderMode mode )
155
- {
156
- // TODO: This will all have to change when we support multiple render modes in the same response
157
- if ( mode == RenderMode . ServerPrerendered || mode == RenderMode . WebAssemblyPrerendered )
158
- {
159
- if ( ! httpContext . Items . TryGetValue ( InvokedRenderModesKey , out var result ) )
160
- {
161
- result = new InvokedRenderModes ( mode is RenderMode . ServerPrerendered ?
162
- InvokedRenderModes . Mode . Server :
163
- InvokedRenderModes . Mode . WebAssembly ) ;
164
-
165
- httpContext . Items [ InvokedRenderModesKey ] = result ;
166
- }
167
- else
168
- {
169
- var currentInvocation = mode is RenderMode . ServerPrerendered ?
170
- InvokedRenderModes . Mode . Server :
171
- InvokedRenderModes . Mode . WebAssembly ;
172
-
173
- var invokedMode = ( InvokedRenderModes ) result ! ;
174
- if ( invokedMode . Value != currentInvocation )
175
- {
176
- invokedMode . Value = InvokedRenderModes . Mode . ServerAndWebAssembly ;
177
- }
178
- }
179
- }
180
- }
181
-
182
- internal static InvokedRenderModes . Mode GetPersistStateRenderMode ( HttpContext httpContext )
183
- {
184
- return httpContext . Items . TryGetValue ( InvokedRenderModesKey , out var result )
185
- ? ( ( InvokedRenderModes ) result ! ) . Value
186
- : InvokedRenderModes . Mode . None ;
187
- }
188
-
189
92
private async ValueTask < IHtmlAsyncContent > StaticComponentAsync ( HttpContext context , Type type , ParameterView parametersCollection )
190
93
{
191
94
var htmlComponent = await PrerenderComponentCoreAsync (
192
95
parametersCollection ,
193
96
context ,
194
97
type ) ;
195
- return new PrerenderedComponentHtmlContent ( _htmlRenderer . Dispatcher , htmlComponent , null , null ) ;
98
+ return new PrerenderedComponentHtmlContent ( Dispatcher , htmlComponent , null , null ) ;
196
99
}
197
100
198
101
private async Task < IHtmlAsyncContent > PrerenderedServerComponentAsync ( HttpContext context , ServerComponentInvocationSequence invocationId , Type type , ParameterView parametersCollection )
@@ -217,7 +120,7 @@ private async Task<IHtmlAsyncContent> PrerenderedServerComponentAsync(HttpContex
217
120
context ,
218
121
type ) ;
219
122
220
- return new PrerenderedComponentHtmlContent ( _htmlRenderer . Dispatcher , htmlComponent , marker , null ) ;
123
+ return new PrerenderedComponentHtmlContent ( Dispatcher , htmlComponent , marker , null ) ;
221
124
}
222
125
223
126
private async ValueTask < IHtmlAsyncContent > PrerenderedWebAssemblyComponentAsync ( HttpContext context , Type type , ParameterView parametersCollection )
@@ -232,7 +135,7 @@ private async ValueTask<IHtmlAsyncContent> PrerenderedWebAssemblyComponentAsync(
232
135
context ,
233
136
type ) ;
234
137
235
- return new PrerenderedComponentHtmlContent ( _htmlRenderer . Dispatcher , htmlComponent , null , marker ) ;
138
+ return new PrerenderedComponentHtmlContent ( Dispatcher , htmlComponent , null , marker ) ;
236
139
}
237
140
238
141
private IHtmlAsyncContent NonPrerenderedServerComponent ( HttpContext context , ServerComponentInvocationSequence invocationId , Type type , ParameterView parametersCollection )
@@ -315,65 +218,4 @@ public void WriteTo(TextWriter writer, HtmlEncoder encoder)
315
218
}
316
219
}
317
220
}
318
-
319
- private static async Task InitializeStandardComponentServicesAsync ( HttpContext httpContext )
320
- {
321
- var navigationManager = ( IHostEnvironmentNavigationManager ) httpContext . RequestServices . GetRequiredService < NavigationManager > ( ) ;
322
- navigationManager ? . Initialize ( GetContextBaseUri ( httpContext . Request ) , GetFullUri ( httpContext . Request ) ) ;
323
-
324
- var authenticationStateProvider = httpContext . RequestServices . GetService < AuthenticationStateProvider > ( ) as IHostEnvironmentAuthenticationStateProvider ;
325
- if ( authenticationStateProvider != null )
326
- {
327
- var authenticationState = new AuthenticationState ( httpContext . User ) ;
328
- authenticationStateProvider . SetAuthenticationState ( Task . FromResult ( authenticationState ) ) ;
329
- }
330
-
331
- // It's important that this is initialized since a component might try to restore state during prerendering
332
- // (which will obviously not work, but should not fail)
333
- var componentApplicationLifetime = httpContext . RequestServices . GetRequiredService < ComponentStatePersistenceManager > ( ) ;
334
- await componentApplicationLifetime . RestoreStateAsync ( new PrerenderComponentApplicationStore ( ) ) ;
335
- }
336
-
337
- private static string GetFullUri ( HttpRequest request )
338
- {
339
- return UriHelper . BuildAbsolute (
340
- request . Scheme ,
341
- request . Host ,
342
- request . PathBase ,
343
- request . Path ,
344
- request . QueryString ) ;
345
- }
346
-
347
- private static string GetContextBaseUri ( HttpRequest request )
348
- {
349
- var result = UriHelper . BuildAbsolute ( request . Scheme , request . Host , request . PathBase ) ;
350
-
351
- // PathBase may be "/" or "/some/thing", but to be a well-formed base URI
352
- // it has to end with a trailing slash
353
- return result . EndsWith ( '/' ) ? result : result += "/" ;
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
- }
379
221
}
0 commit comments