dotnet / dotnet/roslyn

[Blazor] Support compact field syntax for expression-typed component parameters (<Label For="product.Name" />)

Open
#85,658 3 comments 2 reactions 0 assignees View on GitHub
Area-Razor Area-Razor-Compiler untriaged
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

Blazor's built-in form components take the field they operate on as an `Expression>`, which forces every call site to write a lambda:

```razor



```

Two problems with this:

**It's inconsistent within a single form.** `@bind-Value="product.Name"` takes the field directly, while `For="() => product.Name"` on the adjacent line does not — even though `@bind-Value` is itself producing an `Expression>` for `InputBase.ValueExpression` behind the scenes. Users reasonably expect these to look the same.

**It's a step backwards from MVC.** The equivalent tag helpers have no ceremony:

```cshtml


```

This matters more in .NET 11 than it used to. We're filling out static SSR specifically so people can build server-rendered forms in Blazor without compromises, and we just added `Label` and `DisplayName` to close the gap with `@Html.LabelFor()` / `@Html.DisplayNameFor()`. Someone moving an MVC or Razor Pages form over to Blazor SSR hits this on the first line they write, and on every field after that.

To be clear, `Expression>` is the right parameter type — the components walk the expression tree to recover the member name and its `[Display]` / `[DisplayName]` metadata, which a plain `Func` can't provide. The problem is purely the syntax required at the call site.

### Describe the solution you'd like

Let the field be written directly, with the lambda inferred:

```razor



```

Scenario requirements:

- **Consistent across all built-in components that take a field.** At minimum `Label.For`, `DisplayName.For`, `ValidationMessage.For`, and `InputBase.ValueExpression`. Partial adoption would make the inconsistency worse rather than better — the point is that a form reads uniformly.
- **Available to third-party and app components too**, not hardcoded to the built-in set. Component libraries have the same problem, and anyone writing their own form components should be able to opt a parameter in.
- **Non-breaking.** `For="() => product.Name"` must keep working unchanged, as must passing a pre-built expression (`For="_nameExpr"`). This can't churn existing code — `ValidationMessage` has shipped since 3.x.
- **Works with the existing generic components**, with `TValue` still inferred at the call site without the user writing it out.
- **Tooling keeps up.** IntelliSense, the `EditorRequired` analyzer, and error messages should understand both forms, and a mistyped member should still produce a clear error pointing at the right place in the `.razor` file.

Explicitly *not* asking for a new directive attribute (e.g. `@for="..."`). The goal is that the ordinary parameter accepts the compact form, so it generalizes to any expression-typed parameter instead of adding per-concept syntax.

### Additional context

A few notes on why this looks tractable — the actual design is the engineering team's call:

- **Razor already does this transformation.** For `@bind-Value="product.Price"` the generated code emits both the value and a synthesized `() => product.Price` for `ValueExpression`. The capability exists; it's just tied to `@bind` rather than being available to expression-typed parameters generally.
- **There's precedent for the general idea inside Razor.** MVC tag helpers special-case bound attributes typed `ModelExpression` and rewrite the attribute value into a lambda against the model — that's exactly why `asp-for="Name"` works ([`LabelTagHelper.For`](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.TagHelpers/src/LabelTagHelper.cs)).
- **A non-breaking rule exists in principle:** apply the sugar only where the written expression wouldn't otherwise bind to the parameter type. Anything that compiles today keeps compiling and keeps its current meaning; only what is a compile error today starts working. That makes enabling this on already-shipped APIs like `ValidationMessage.For` a widening change rather than a breaking one.
- Likely needs coordination with dotnet/razor, since the work would land in the Razor compiler rather than in the components.
- Related: dotnet/aspnetcore#49147 (`DisplayNameFor` support in Blazor), which added the `DisplayName` component but didn't cover call-site ergonomics.

Affected APIs today:

| Component | Parameter | Shipped in |
|---|---|---|
| `ValidationMessage` | `For` | 3.x |
| `InputBase` | `ValueExpression` | 3.x |
| `Label` | `For` | .NET 11 |
| `DisplayName` | `For` | .NET 11 |

Contributor guide

Open the contributing guide

Research direction

Start by reading the referenced MVC/Mvc.TagHelpers/src/LabelTagHelper.cs LabelTagHelper.For implementation and the affected Blazor parameters: Label.For, DisplayName.For, ValidationMessage.For, and InputBase.ValueExpression. Coordinate with dotnet/razor to locate the compiler entry point; done means compact expressions work across generic expression-typed parameters while existing lambdas and expressions, inference, diagnostics, and tooling remain supported.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers, tooling, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.