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