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
- Ensure AddElementReferenceCapture is called strictly between OpenElement(...) and its matching CloseElement().
- Audit every Open*/Close* pair in the RenderFragment for balance — an early CloseElement is the usual cause.
- 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
- Never call AddElementReferenceCapture outside an OpenElement/CloseElement pair.
- Keep element-ref captures immediately after the OpenElement they target, before any child content.
- In .razor, only put @ref on HTML elements; for components use component @ref (handled by a different API).
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
- Component reference captures may only be added as children o
- Named events may only be added as children of frames of type
- Attributes may only be added immediately after frames of typ
- Component parameters may only be added immediately after fra
- There is no enclosing component frame.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/1586de2082315b9c.
Report an issue: GitHub.