dotnet/aspnetcore · critical · InvalidOperationException

Render output is invalid for component of type '{component.G

Error message

Render output is invalid for component of type '{component.GetType().FullName}'. A frame of type '{invalidFrame.FrameType}' was left unclosed. Do not use try/catch inside rendering logic, because partial output cannot be undone.

What it means

Thrown by RenderTreeBuilder.AssertTreeIsValid (called after a component render). If _openElementIndices still has entries, some OpenElement/OpenComponent/OpenRegion was never matched by its Close*. An unclosed frame would corrupt diffing, so the entire render is rejected. The message explicitly forbids try/catch in render logic because partial output cannot be rolled back.

Source

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

        ref var frame = ref frames[frameIndex];
        if (frame.FrameTypeField != RenderTreeFrameType.Attribute)
        {
            throw new InvalidOperationException(
                $"The frame at index {frameIndex} is of type '{frame.FrameTypeField}', not '{RenderTreeFrameType.Attribute}'.");
        }

        frame.AttributeValueField = value;
    }

    internal void AssertTreeIsValid(IComponent component)
    {
        if (_openElementIndices.Count > 0)
        {
            // It's never valid to leave an element/component/region unclosed. Doing so
            // could cause undefined behavior in diffing.
            ref var invalidFrame = ref _entries.Buffer[_openElementIndices.Peek()];
            throw new InvalidOperationException($"Render output is invalid for component of type '{component.GetType().FullName}'. A frame of type '{invalidFrame.FrameType}' was left unclosed. Do not use try/catch inside rendering logic, because partial output cannot be undone.");
        }
    }

    // Internal for testing
    internal void ProcessDuplicateAttributes(int first)
    {
        Debug.Assert(_hasSeenAddMultipleAttributes);

        // When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
        // the element/component. However, we also don't know the end index we should look at because it
        // will contain nested content.
        var buffer = _entries.Buffer;
        var last = _entries.Count - 1;

        for (var i = first; i <= last; i++)
        {
            if (buffer[i].FrameTypeField != RenderTreeFrameType.Attribute)
            {

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Make every Open* strictly paired with a Close* in the same control-flow path; prefer try/finally around the Close if needed for cleanup (though the guidance is to never throw in render logic).
  2. Remove all try/catch from RenderFragment bodies — fix the underlying throw instead so partial output never occurs.
  3. Audit conditionals/early-returns/loops that contain Open* without their matching Close*.
  4. If using regions, ensure each OpenRegion has a CloseRegion even on all branches.

Example fix

// before
builder.OpenElement(0, "div");
if (show) { builder.AddContent(1, "x"); return; } // CloseElement skipped
builder.CloseElement();

// after
builder.OpenElement(0, "div");
if (show) { builder.AddContent(1, "x"); }
builder.CloseElement();
Defensive patterns

Strategy: validation

Validate before calling

// Do NOT try/catch in render logic. Instead guarantee balanced Open/Close structurally.
// A debug assertion helper you can call inside a RenderFragment:
#if DEBUG
void AssertBalanced(RenderTreeBuilder b) { /* mirror your opens/closes in a debug counter */ }
#endif
// The real fix is structural: every Open* has a matching Close* on all paths.

Prevention

When it happens

Trigger: Any render fragment where an Open* call lacks a matching Close* — a missing CloseElement/CloseComponent/CloseRegion, an early return inside the fragment, or an exception thrown between Open and Close that unwinds the stack. The throw fires on the next render attempt.

Common situations: A conditional `if (cond) builder.OpenElement(...);` whose CloseElement is outside the if; a try/catch inside a RenderFragment that swallows an error after an Open; an exception in nested rendering that leaves the builder half-populated; miscounted open/close after a refactor.

Related errors


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