dotnet/aspnetcore · error · InvalidOperationException

Element reference captures may only be added as children of

Error message

Element reference captures may only be added as children of frames of type Element

What it means

Thrown by RenderTreeBuilder.AddElementReferenceCapture (the frame emitted for an HTML-element @ref capture). The capture frame is a child of its parent frame, and the parent must be an Element frame opened via OpenElement. The render-tree diff requires the captured reference to resolve to a real DOM element, so any other parent type (Component, Region, or no parent) is an invariant violation.

Source

Thrown at src/Components/Components/src/Rendering/RenderTreeBuilder.cs:600

        // if necessary.
        if (_hasSeenAddMultipleAttributes)
        {
            ProcessDuplicateAttributes(first: indexOfEntryBeingClosed + 1);
        }

        _entries.Buffer[indexOfEntryBeingClosed].ComponentSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
    }

    /// <summary>
    /// Appends a frame representing an instruction to capture a reference to the parent element.
    /// </summary>
    /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
    /// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
    public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
    {
        if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
        {
            throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
        }

        _entries.AppendElementReferenceCapture(sequence, elementReferenceCaptureAction);
        _lastNonAttributeFrameType = RenderTreeFrameType.ElementReferenceCapture;
    }

    /// <summary>
    /// Appends a frame representing an instruction to capture a reference to the parent component.
    /// </summary>
    /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
    /// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
    public void AddComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction)
    {
        var parentFrameIndex = GetCurrentParentFrameIndex();
        if (!parentFrameIndex.HasValue)
        {
            throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
        }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Ensure AddElementReferenceCapture is called strictly between OpenElement(...) and its matching CloseElement().
  2. Audit every Open*/Close* pair in the RenderFragment for balance — an early CloseElement is the usual cause.
  3. If authoring .razor, confirm the @ref is on an HTML element (<input @ref=...>) and not on a component (<MyComp @ref=...> uses AddComponentReferenceCapture instead).

Example fix

// before
builder.AddElementReferenceCapture(1, r => _ref = r);
builder.OpenElement(0, "input");
...
builder.CloseElement();

// after
builder.OpenElement(0, "input");
...
builder.AddElementReferenceCapture(1, r => _ref = r);
builder.CloseElement();
Defensive patterns

Strategy: validation

Validate before calling

// RenderTreeBuilder has no public parent-type accessor. In manual RenderFragment
code, track your own nesting depth to guarantee the capture runs inside an element.
int _elementDepth = 0;
void Render(RenderTreeBuilder b) {
    b.OpenElement(0, "input"); _elementDepth++;
    if (_elementDepth > 0) b.AddElementReferenceCapture(1, r => _ref = r);
    _elementDepth--; b.CloseElement();
}

Prevention

When it happens

Trigger: Calling builder.AddElementReferenceCapture(seq, action) when GetCurrentParentFrameType() != Element — i.e., at the root of a render fragment (empty stack), inside OpenComponent(...)/CloseComponent(), or inside OpenRegion(...)/CloseRegion(). The .razor compiler emits this via @ref on an HTML tag; the manual-call equivalent is misplaced.

Common situations: Hand-written RenderFragment/Razor lib code that forgets OpenElement before the capture; a mismatched CloseElement that popped the element before the capture ran; a refactor that moved an @ref onto a component instead of an element.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/1586de2082315b9c. Report an issue: GitHub.