{"record":{"id":"b3264d315089cc47","repo":"dotnet/aspnetcore","slug":"render-output-is-invalid-for-component-of-type-c","errorCode":null,"errorMessage":"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.","messagePattern":"Render output is invalid for component of type '(.+?)'\\. A frame of type '(.+?)' was left unclosed\\. Do not use try/catch inside rendering logic, because partial output cannot be undone\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Components/Components/src/Rendering/RenderTreeBuilder.cs","lineNumber":817,"sourceCode":"\n        ref var frame = ref frames[frameIndex];\n        if (frame.FrameTypeField != RenderTreeFrameType.Attribute)\n        {\n            throw new InvalidOperationException(\n                $\"The frame at index {frameIndex} is of type '{frame.FrameTypeField}', not '{RenderTreeFrameType.Attribute}'.\");\n        }\n\n        frame.AttributeValueField = value;\n    }\n\n    internal void AssertTreeIsValid(IComponent component)\n    {\n        if (_openElementIndices.Count > 0)\n        {\n            // It's never valid to leave an element/component/region unclosed. Doing so\n            // could cause undefined behavior in diffing.\n            ref var invalidFrame = ref _entries.Buffer[_openElementIndices.Peek()];\n            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.\");\n        }\n    }\n\n    // Internal for testing\n    internal void ProcessDuplicateAttributes(int first)\n    {\n        Debug.Assert(_hasSeenAddMultipleAttributes);\n\n        // When AddMultipleAttributes method has been called, we need to postprocess attributes while closing\n        // the element/component. However, we also don't know the end index we should look at because it\n        // will contain nested content.\n        var buffer = _entries.Buffer;\n        var last = _entries.Count - 1;\n\n        for (var i = first; i <= last; i++)\n        {\n            if (buffer[i].FrameTypeField != RenderTreeFrameType.Attribute)\n            {","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Components/Components/src/Rendering/RenderTreeBuilder.cs#L799-L835","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Remove all try/catch from RenderFragment bodies — fix the underlying throw instead so partial output never occurs.","Audit conditionals/early-returns/loops that contain Open* without their matching Close*.","If using regions, ensure each OpenRegion has a CloseRegion even on all branches."],"exampleFix":"// before\nbuilder.OpenElement(0, \"div\");\nif (show) { builder.AddContent(1, \"x\"); return; } // CloseElement skipped\nbuilder.CloseElement();\n\n// after\nbuilder.OpenElement(0, \"div\");\nif (show) { builder.AddContent(1, \"x\"); }\nbuilder.CloseElement();","handlingStrategy":"validation","validationCode":"// Do NOT try/catch in render logic. Instead guarantee balanced Open/Close structurally.\n// A debug assertion helper you can call inside a RenderFragment:\n#if DEBUG\nvoid AssertBalanced(RenderTreeBuilder b) { /* mirror your opens/closes in a debug counter */ }\n#endif\n// The real fix is structural: every Open* has a matching Close* on all paths.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never wrap render-fragment bodies in try/catch — the framework cannot roll back partial output; fix the root cause instead.","Ensure every OpenElement/OpenComponent/OpenRegion has a matching Close on every code path (including early returns and exceptions).","Avoid early returns between an Open and its Close; close before returning.","In generated/templated trees, unit-test for balance by counting open vs close calls."],"tags":["blazor","rendering","rendertree","unclosed-frame","render-invariant"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}