Hey guys, I updated my packages DotVVM 5.0.1 and Bootstrat4 5.0.0 and had a page crash out with a bs radio button control. I use claude code and had it help prepare an .md file describing my issue in detail (copied herein as I could not attach it). Hope it helps! Keep up the good work guys, I do love DotVVM ![]()
Cheers/Ben
## RadioButton/CheckBox with `IncludeInPage=“false”` throws “Cannot call HtmlWriter.RenderEndTag, since attributes were added into the writer” (DotVVM.Controls.Bootstrap4 5.0.0)
### Environment
- DotVVM: 5.0.1
- DotVVM.Controls.Bootstrap4: 5.0.0
- DotVVM.Contrib.Select2: 5.0.0
- DotVVM.Contrib.LoadablePanel: 5.0.0
- DotVVM.Contrib.BootstrapColorpicker: 5.0.0
- DotVVM.HotReload.AspNetCore: 5.0.1
- Target framework: net10.0
- .NET SDK: 10.0.400
- Hosting: ASP.NET Core (Microsoft.NET.Sdk.Web)
- Upgraded from DotVVM 4.3.9 / DotVVM.Controls.Bootstrap4 4.3.0, where this markup worked correctly.
### Summary
Any `bs:RadioButton` or `bs:CheckBox` with an `IncludeInPage` value binding that evaluates to `false` on the server crashes the whole page render with:
```
DotVVM.Framework.Controls.DotvvmControlException: Error occurred in HtmlGenericControl.Render method
—> System.InvalidOperationException: Cannot call HtmlWriter.RenderEndTag, since attributes were added into the writer. Attributes: class
at DotVVM.Framework.Controls.HtmlWriter.RenderEndTag()
at DotVVM.Framework.Controls.DotvvmControl.RenderControl(IHtmlWriter writer, IDotvvmRequestContext context)
at DotVVM.Framework.Controls.DotvvmControl.Render(IHtmlWriter writer, IDotvvmRequestContext context)
...
```
The exception’s `Control` context points at an *unrelated* ancestor element (in our case, `bs:FormGroup`'s content-column wrapper `
### Root cause
Decompiling `DotVVM.Controls.Bootstrap4` 5.0.0 (`DotVVM.Framework.Controls.Bootstrap.dll`), both `RadioButton.RenderControl()` and `CheckBox.RenderControl()` override the low-level `RenderControl` extension point instead of `AddAttributesToRender`, and call `writer.AddAttribute(“class”, …)` **unconditionally, before** deferring to the base implementation:
```csharp
// DotVVM.Framework.Controls.Bootstrap4.RadioButton
protected override void RenderControl(IHtmlWriter writer, IDotvvmRequestContext context)
{
writer.AddAttribute("class", base.Classes.RadioButton.GetRadioButtonStyle(FormControlStyle), true, null);
if (FormControlStyle == BootstrapFormStyle.BootstrapCustom)
writer.AddAttribute("class", base.Classes.RadioButton.RadioButtonBootstrapCustom, true, null);
if (IsInline)
writer.AddAttribute("class", base.Classes.RadioButton.GetRadioButtonInlineStyle(FormControlStyle), true, null);
((DotvvmControl)this).RenderControl(writer, context); // base.RenderControl - this is where IncludeInPage is actually checked
}
```
`CheckBox.RenderControl()` has the identical shape.
In DotVVM 5’s core (`DotvvmControl.RenderControl`), `IncludeInPage` is evaluated *inside* the base implementation, not before it’s called:
```csharp
protected virtual void RenderControl(IHtmlWriter writer, IDotvvmRequestContext context)
{
RenderState r = default;
foreach (var (prop, value) in properties)
TouchProperty(prop, value, ref r);
if (RenderBeforeControl(in r, writer, context))
{
if (r.IncludeInPage is not IValueBinding) return;
RenderAsTemplate<ValueTuple>(in r, writer, context); // <-- new in 5.0: renders into an \*isolated\* writer/StringWriter as a knockout template
}
else
{
AddAttributesToRender(writer, context);
RenderBeginTag(writer, context);
RenderContents(writer, context);
RenderEndTag(writer, context);
RenderAfterControl(in r, writer);
}
}
```
When `IncludeInPage` evaluates to `false`, DotVVM 5 now renders the control as a Knockout template resource using a brand-new `HtmlWriter` wrapping a `StringWriter` (`RenderAsTemplate`), rather than touching the *outer* writer at all - this is the new “no more flash of uninitialized content” behavior.
The problem: `RadioButton`/`CheckBox` already called `writer.AddAttribute(“class”, …)` on the **outer** writer before `base.RenderControl` ran, i.e. before it knew `IncludeInPage` would take the `RenderAsTemplate` branch. Since `RenderAsTemplate` never opens a tag on the *outer* writer (it only opens tags on its own private `templateWriter`), that queued `class` attribute is never flushed and stays stuck in the outer writer’s pending-attributes list.
Rendering then continues normally with the RadioButton/CheckBox’s siblings, until the *next* ancestor element on the outer writer calls `RenderEndTag()` (in our repro, the enclosing `bs:FormGroup`'s content `
This appears to affect any `RadioButton`/`CheckBox` with `IncludeInPage` bound to a value that can be `false`. Other Bootstrap4 controls we checked (`Button`, `LinkButton`, `DropDownButton`, `ComboBox`, `TextBox`, `RouteLink`, `ButtonGroup`, `InputGroup`, `Alert`, `Badge`) implement `AddAttributesToRender` instead and don’t have this pattern, so they appear unaffected - `RadioButton`/`CheckBox` (both derived from `CheckableControlBase`) may be the only ones with this specific issue, but it’s worth checking any other control that overrides `RenderControl` directly.
### Minimal repro
```dothtml
<bs:Form Type=“Horizontal” LabelSizeMD=“2”>
<bs:FormGroup LabelText="Options">
<bs:RadioButton IncludeInPage="{value: SomeNullableThing != null}"
CheckedItem="{value: SelectedOption}"
CheckedValue="opt1"
Text="Option 1" />
<bs:RadioButton CheckedItem="{value: SelectedOption}"
CheckedValue="opt2"
Text="Option 2" />
</bs:FormGroup>
</bs:Form>
```
with a view model where `SomeNullableThing` is `null` on first render. Loading the page throws the exception above.
### Workaround
Move `IncludeInPage` off the `RadioButton`/`CheckBox` itself and onto a wrapping element that doesn’t have this bug (e.g. a plain `` or `dot:Decorator`):
```dothtml
<bs:RadioButton CheckedItem="{value: SelectedOption}" CheckedValue="opt1" Text="Option 1" />
```
This works because the `RadioButton` no longer has its own `IncludeInPage`, so it always takes the normal (non-template) render path and its directly-added `class` attribute gets flushed correctly by its own `RenderBeginTag`.
### Suggested fix
`RadioButton.RenderControl()` / `CheckBox.RenderControl()` should add their `class` attributes via an `AddAttributesToRender` override instead of directly on the writer inside `RenderControl`, so they’re only ever added on whichever writer (outer or template) the base implementation actually decides to use.