dotnet/aspnetcore · critical · InvalidOperationException

Render output is invalid for component of type

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

AssertTreeIsValid runs at the end of a component render and enforces that every OpenElement/OpenComponent/OpenRegion has a matching Close. The _openElementIndices stack must be empty when rendering finishes; any entry left on it means a structural frame was opened but never closed, which would cause undefined behavior in diffing. The exception names the offending component type and the leftover frame type, and explicitly warns against try/catch in rendering logic because partial tree output cannot be rolled back.

Solutions

  1. Audit the component's BuildRenderTree for every Open* and confirm each path — including early returns and exception handlers — reaches the matching Close*.
  2. Remove try/catch blocks from rendering logic; if you must guard work, do it before opening frames.
  3. Use the IDE to pair-match Open/Close calls, or extract each sub-tree into its own RenderFragment so the builder scope is bounded.
  4. Reproduce with the offending component in isolation and step through to find the unbalanced Close.

Example fix

// before
builder.OpenElement(0, "ul");
foreach (var item in items) {
    if (item == null) return; // BUG: leaves <ul> open
    builder.OpenElement(1, "li"); builder.AddContent(2, item); builder.CloseElement();
}
builder.CloseElement();
// after
builder.OpenElement(0, "ul");
foreach (var item in items) {
    if (item == null) continue;
    builder.OpenElement(1, "li"); builder.AddContent(2, item); builder.CloseElement();
}
builder.CloseElement();
Defensive patterns

Strategy: validation

Validate before calling

// Render fragments cannot self-validate mid-build, so validate the source structure:
// every Open* must have a matching Close* on every control-flow path. Use static
// analysis or code review; do not wrap BuildRenderTree in try/catch.

Type guard

// Helper to ensure balanced open/close in hand-written trees.
using var scope = OpenElement(builder, 0, "div");
// ... children ...
// Dispose() of the scope closes the element even on early return.
// Implement IDisposable to call builder.CloseElement().

Try / catch

// A render-time InvalidOperationException here is unrecoverable for that render —
// the partial tree cannot be undone. Do NOT try/catch around BuildRenderTree.
// Instead, fix the source so the tree is always balanced, and surface the exception
// through the host's global error boundary (<ErrorBoundary>).

Prevention

When it happens

Trigger: A RenderFragment or BuildRenderTree that calls OpenElement/OpenComponent/OpenRegion but returns (or throws) before the matching CloseElement/CloseComponent/CloseRegion. Also triggered by a try/catch inside BuildRenderTree that swallows an exception and returns a half-built tree.

Common situations: Forgetting CloseComponent() after a conditional branch that returns early; refactoring a render method and dropping a Close call; using try/catch around Open/Close pairs to suppress an exception — the suppressed exception leaves the stack unbalanced; control-flow bugs where a loop opens elements but closes them only on the happy path.

Related errors


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

Appendix: 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 3600ca084e)