File: src\aspnetcore\src\Components\Shared\src\RenderFragmentCapture.cs
Web Access
Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
 
namespace Microsoft.AspNetCore.Components;
 
// Wrapper for one RenderFragment instance. Captures the frames produced by the original
// fragment, and tracks any nested RenderFragment parameters discovered inside those frames
// as child captures keyed by their attribute frame index inside the snapshot.
internal sealed class RenderFragmentCapture
{
    private readonly RenderFragment _original;
    private readonly Dictionary<int, RenderFragmentCapture> _childCaptures = new();
    private RenderTreeFrame[]? _capturedFrames;
 
    public RenderFragmentCapture(RenderFragment original)
    {
        _original = original;
    }
 
    public RenderFragmentCapture(RenderTreeFrame[] capturedFrames)
    {
        _original = static _ => { };
        _capturedFrames = capturedFrames;
    }
 
    public IReadOnlyDictionary<int, RenderFragmentCapture> ChildCaptures => _childCaptures;
 
    public void Invoke(RenderTreeBuilder builder)
    {
        var start = builder.GetFrames().Count;
        _original(builder);
        var end = builder.GetFrames().Count;
 
        // Walk the produced frames and wrap any nested RenderFragment component parameters.
        // Child captures are keyed by index relative to the snapshot start, so the keys
        // line up with the positions seen by the serializer when it walks _capturedFrames.
        WrapNestedFragments(builder, start, end);
 
        // Re-fetch frames after WrapNestedFragments may have mutated attribute values.
        var frames = builder.GetFrames();
        var count = end - start;
        _capturedFrames = new RenderTreeFrame[count];
        Array.Copy(frames.Array, start, _capturedFrames, 0, count);
    }
 
    public RenderTreeFrame[] GetCapturedFrames()
    {
        if (_capturedFrames is null)
        {
            throw new InvalidOperationException("Cannot retrieve captured frames because the RenderFragment was not invoked during rendering.");
        }
        return _capturedFrames;
    }
 
    private void WrapNestedFragments(RenderTreeBuilder builder, int start, int end)
    {
        var frames = builder.GetFrames();
        for (var i = start; i < end; i++)
        {
            ref readonly var frame = ref frames.Array[i];
            if (frame.FrameType is not RenderTreeFrameType.Component)
            {
                continue;
            }
 
            var componentSubtreeEnd = i + frame.ComponentSubtreeLength;
            // A component's attribute frames always sit right after the Component
            // frame in the render tree, with no other frame types mixed in. So we can stop as
            // soon as we hit the first non-Attribute frame.
            for (var j = i + 1; j < componentSubtreeEnd && frames.Array[j].FrameType is RenderTreeFrameType.Attribute; j++)
            {
                ref readonly var attrFrame = ref frames.Array[j];
                if (attrFrame.AttributeValue is RenderFragment innerRf)
                {
                    var innerCapture = new RenderFragmentCapture(innerRf);
                    // Replace the original delegate in the live render buffer with the wrapper.
                    // This is required so that when the nested component later invokes its
                    // RenderFragment parameter, control flows through innerCapture.Invoke and
                    // populates innerCapture._capturedFrames. Looking up the capture by frame
                    // index in _childCaptures would otherwise find an entry whose frames were
                    // never recorded.
#pragma warning disable ASP0032
                    builder.SetAttributeValue(j, (RenderFragment)innerCapture.Invoke);
#pragma warning restore ASP0032
                    _childCaptures[j - start] = innerCapture;
                }
            }
        }
    }
}