@@ -106,6 +106,17 @@ public Task<ComponentRenderedText> RenderComponentAsync<TComponent>(ParameterVie
106
106
protected override void HandleException ( Exception exception )
107
107
=> ExceptionDispatchInfo . Capture ( exception ) . Throw ( ) ;
108
108
109
+ private string RenderTreeToHtmlString ( ArrayRange < RenderTreeFrame > frames , int position , int maxElements )
110
+ {
111
+ var viewBuffer = new ViewBuffer ( _viewBufferScope , nameof ( HtmlRenderer ) , ViewBuffer . ViewPageSize ) ;
112
+ var context = new HtmlRenderingContext ( this , viewBuffer , _serviceProvider ) ;
113
+ RenderFrames ( context , frames , position , maxElements ) ;
114
+
115
+ using var sw = new StringWriter ( ) ;
116
+ viewBuffer . WriteTo ( sw , HtmlEncoder . Default ) ;
117
+ return sw . GetStringBuilder ( ) . ToString ( ) ;
118
+ }
119
+
109
120
private int RenderFrames ( HtmlRenderingContext context , ArrayRange < RenderTreeFrame > frames , int position , int maxElements )
110
121
{
111
122
var nextPosition = position ;
@@ -306,7 +317,7 @@ private static int RenderAttributes(
306
317
private ViewBuffer GetRenderedHtmlContent ( int componentId )
307
318
{
308
319
var viewBuffer = new ViewBuffer ( _viewBufferScope , nameof ( HtmlRenderer ) , ViewBuffer . ViewPageSize ) ;
309
- var context = new HtmlRenderingContext ( viewBuffer , _serviceProvider ) ;
320
+ var context = new HtmlRenderingContext ( this , viewBuffer , _serviceProvider ) ;
310
321
311
322
var frames = GetCurrentRenderTreeFrames ( componentId ) ;
312
323
var newPosition = RenderFrames ( context , frames , 0 , frames . Count ) ;
@@ -368,12 +379,14 @@ public void AppendEpilogue(ViewBuffer htmlContentBuilder)
368
379
private sealed class HtmlRenderingContext
369
380
{
370
381
private readonly IServiceProvider _serviceProvider ;
382
+ private readonly HtmlRenderer _htmlRenderer ;
371
383
private ServerComponentSerializer _serverComponentSerializer ;
372
384
private ServerComponentInvocationSequence _serverComponentSequence ;
373
385
374
- public HtmlRenderingContext ( ViewBuffer viewBuffer , IServiceProvider serviceProvider )
386
+ public HtmlRenderingContext ( HtmlRenderer htmlRenderer , ViewBuffer viewBuffer , IServiceProvider serviceProvider )
375
387
{
376
388
HtmlContentBuilder = viewBuffer ;
389
+ _htmlRenderer = htmlRenderer ;
377
390
_serviceProvider = serviceProvider ;
378
391
}
379
392
@@ -399,13 +412,13 @@ public InteractiveComponentMarker SerializeInvocation(ArrayRange<RenderTreeFrame
399
412
_serverComponentSequence = new ( ) ;
400
413
}
401
414
402
- serverMarker = _serverComponentSerializer . SerializeInvocation ( _serverComponentSequence , componentFrame . ComponentType , parameters , prerendered : true ) ;
415
+ serverMarker = _serverComponentSerializer . SerializeInvocation ( _serverComponentSequence , componentFrame . ComponentType , parameters , prerendered : true , PrepareRenderFragment ) ;
403
416
}
404
417
405
418
if ( renderModeNumericValue == WebComponentRenderMode . WebAssembly . NumericValue
406
419
|| renderModeNumericValue == WebComponentRenderMode . Auto . NumericValue )
407
420
{
408
- webAssemblyMarker = WebAssemblyComponentSerializer . SerializeInvocation ( componentFrame . ComponentType , parameters , prerendered : true ) ;
421
+ webAssemblyMarker = WebAssemblyComponentSerializer . SerializeInvocation ( componentFrame . ComponentType , parameters , prerendered : true , PrepareRenderFragment ) ;
409
422
}
410
423
411
424
if ( ! serverMarker . HasValue && ! webAssemblyMarker . HasValue )
@@ -415,6 +428,76 @@ public InteractiveComponentMarker SerializeInvocation(ArrayRange<RenderTreeFrame
415
428
416
429
return new InteractiveComponentMarker ( serverMarker , webAssemblyMarker ) ;
417
430
}
431
+
432
+ private string PrepareRenderFragment ( RenderFragment fragment )
433
+ {
434
+ // We can't just execute the RenderFragment delegate directly. We have to run it through the
435
+ // renderer so that the renderer can do all the normal things to activate child components
436
+ // and run their full lifecycle (disposal, etc.)
437
+ var rootComponent = new FragmentRenderer { Fragment = fragment } ;
438
+ string initialHtml = null ;
439
+ var renderTask = _htmlRenderer . Dispatcher . InvokeAsync ( async ( ) =>
440
+ {
441
+ // WARNING: THIS IS NOT CORRECT AND CAN CAUSE AN INFINITE LOOP
442
+ // We should *not* really be creating new root components as a side-effect of
443
+ // parameter serialization, because that means the child content within this
444
+ // RenderFragment subtree is recreated on each parameter serialization and may
445
+ // be repeating work, or worse, keep causing ancestor components to re-render
446
+ // and hence an infinite loop.
447
+ // Instead, all this work should be done in a completely different place: the
448
+ // diffing system. When the diffing system sees that a child component has "interactive"
449
+ // rendermode, any RenderFragment parameters should be handled by creating something
450
+ // like a FragmentRenderer instance as a new component and referencing it from the
451
+ // the parameter's attribute frame. Then on successive diffs, we can reuse the
452
+ // FragmentRenderer and hence preserve descendant components. Also this means we would
453
+ // no longer be doing the RenderFragment rendering during parameter serialization: we'd
454
+ // be doing it during diffing, so it would be integrated into normal the rendering flow.
455
+ // The parameter serialization would then only need to know that for this special parameter
456
+ // type, it can find the associated FragmentRenderer that already exists, and serialize
457
+ // its render frames that already exist. And I think that means we don't have to deal
458
+ // with asynchrony during the parameter serialization at all.
459
+ var rootComponentId = _htmlRenderer . AssignRootComponentId ( rootComponent ) ;
460
+ var renderTask = _htmlRenderer . RenderRootComponentAsync ( rootComponentId ) ;
461
+
462
+ var frames = _htmlRenderer . GetCurrentRenderTreeFrames ( rootComponentId ) ;
463
+ initialHtml = _htmlRenderer . RenderTreeToHtmlString ( frames , 0 , frames . Count ) ;
464
+ } ) ;
465
+
466
+ // TODO: Include renderTask in the set of tasks we'd await if the top-level call
467
+ // asked us to await quiescence. And if we're not awaiting quiescence, at least
468
+ // merge it into the overall top-level returned task for error handling. The following
469
+ // logic is just a cheap stand-in.
470
+ if ( renderTask . IsFaulted )
471
+ {
472
+ throw renderTask . Exception ;
473
+ }
474
+
475
+ if ( ! renderTask . IsCompleted )
476
+ {
477
+ // Fail fast rather than the infinite loop mentioned above.
478
+ throw new InvalidOperationException ( "The current implementation of RenderFragment serialization doesn't support child content that performs async work." ) ;
479
+ }
480
+
481
+ return initialHtml ;
482
+ }
483
+
484
+ class FragmentRenderer : IComponent
485
+ {
486
+ RenderHandle _renderHandle ;
487
+
488
+ public RenderFragment Fragment { get ; set ; }
489
+
490
+ public void Attach ( RenderHandle renderHandle )
491
+ {
492
+ _renderHandle = renderHandle ;
493
+ }
494
+
495
+ public Task SetParametersAsync ( ParameterView parameters )
496
+ {
497
+ _renderHandle . Render ( Fragment ) ;
498
+ return Task . CompletedTask ;
499
+ }
500
+ }
418
501
}
419
502
420
503
/// <summary>
0 commit comments