Skip to content

Razor: extend literal-attribute shortcut to component parameters typed as a C# union with a string case #13188

Description

@danroth27

Summary

When a Blazor component [Parameter] is typed as a C# union (.NET 11) that includes string as a case, the Razor literal-attribute shortcut does not apply — the attribute value is parsed as a C# expression instead of a string literal. This makes the canonical "Slot accepts text or markup or fragment" scenario much less ergonomic than it needs to be.

Repro

Slot.razor:

@switch (Content.Value)
{
    case string text:             @text;     break;
    case MarkupString markup:     @markup;   break;
    case RenderFragment fragment: @fragment; break;
}

@code {
    [Parameter] public Slot Content { get; set; }
}

Slot is public union Slot(string, MarkupString, RenderFragment);.

@* Does NOT compile — `hello` is parsed as the C# expression `hello` *@
<Slot Content="hello" />

@* Workaround — author must wrap every literal in @("…") *@
<Slot Content="@("hello")" />

Why this matters

This is the headline scenario for C# unions in Blazor: a component-library author exposes a single "slot" parameter that uniformly accepts plain text, raw HTML (MarkupString), or templated content (RenderFragment). The implicit conversions synthesized by the union language feature already make the typed call site ((Slot)"hello") work; only the Razor markup form is missing.

Verified on .NET 11 Preview 6 (11.0.100-preview.6.26315.102) via danroth27/blazor-unions-verify — see Pages/LiteralAttrHost.razor and RazorCompilerEdgeCaseTests.StringLiteralAttribute_OnUnionParameter_TreatedAsExpression.

Today's behaviour

Razor's literal-attribute shortcut is gated on the parameter type being exactly string. Any other declared type triggers the C# expression path, so Value="hello" becomes Value = hello and fails to compile.

Proposed fix

Extend the eligibility check so that a parameter typed as a C# union (a struct implementing IUnion) is eligible for the literal-attribute shortcut if the union has a string case. When emitting the attribute assignment, cast the literal through the union type so the compiler-synthesized implicit conversion runs:

// Today (string parameter type):
__builder.AddComponentParameter(1, "Value", (object)"hello");

// Proposed (union[string] parameter type):
__builder.AddComponentParameter(1, "Value", (object)(Slot)"hello");

This is a Razor-compiler-only change — no framework API changes, no runtime changes. It matches the parallel C# language design where (Slot)"hello" already compiles.

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions