dotnet / dotnet/aspnetcore

[Blazor] `Input*` - allow correct async handling of `ValueChanged` callbacks (incl. `@bind-Value:after`)

Open
#44,105 8 comments 6 reactions 0 assignees View on GitHub
area-blazor enhancement
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

## Summary

In the current `InputBase` design, the binding to underlying `input` element is implemented by synchronous setter (`CurrentValue_set`). The asynchronous `ValueChanged` callback is called from the synchronous setter as "fire and forget" task (line 78):
https://github.com/dotnet/aspnetcore/blob/905557eaca32f5ce9fd17a25e5187ceb7a75cf1c/src/Components/Web/src/Forms/InputBase.cs#L69-L82

Although there already have been less frequent scenarios where this turned out to be an issue (e.g. manual binding using `Value`, `ValueChanged` and `ValueExpression` parameters), starting with `@bind-Value:after` this will become much bigger pain. Subscribtion to the `ValueChanged` callback will become much easier and I expect the users will start using is much more (imagine continuous saving of values when filling the form).

I would like to start discussion on this topic, try to find a new design for `InputBase` and derived components and allow proper asynchronous handling of the `ValueChanged` callback (incl. `@bind-Value:after` variant).

## Motivation and goals

Consider this sample code. The exception is "lost", neither `blazor-error-ui`, nor `ErrorBoundary` or browser/server-console will capture the exception:
```razor
@page "/"




@**@

@code {
string model = String.Empty;

private Task DoSomethingAfterValueChanged()
{
Console.WriteLine("This executes.");
throw new InvalidOperationException("[1] This exception is lost in async-over-sync call from InputBase.CurrentValue_set.");
// exception not logged in Console
// exception not caught by ErrorBoundary
// exception not caught by Blazor global error UI (the yellow strip of death :-D)
}
}
```
(In opposite, the plain `input` HTML element will behave correctly.)

## Risks

It is obvious, that the solution will cause major breaking changes in current `Input*` components as their `protected` API for inherited components would have to change significantly (the `CurrentValue` and `CurrentValueAsString` properties will have to be replaced with some asynchronous subsitutes). With such major impact, consider creating of new set of `NewInput*` components and keep the old ones as they are.

## Examples

Give brief examples of possible developer experiences (e.g., code they would write).

Don't be deeply concerned with how it would be implemented yet. Your examples could even be from other technology stacks.

# Detailed design

I would like to discuss the issue first with major stakeholders (e.g. @SteveSandersonMS, @javiercn, ...) and based on the approval that "this is something we want to solve" we can elaborate more on the detailed design. It is obvious that we have to handle the `onchange` callback from the `input` element in some asynchronous method rather than synchronous property setter (line 38).
https://github.com/dotnet/aspnetcore/blob/905557eaca32f5ce9fd17a25e5187ceb7a75cf1c/src/Components/Web/src/Forms/InputText.cs#L32-L41

Contributor guide

Open the contributing guide

Research direction

Start by reading src/Components/Web/src/Forms/InputBase.cs and InputText.cs at the linked locations, then reproduce the Razor sample with @bind-Value:after and compare it with the plain input behavior. The issue is currently asking for stakeholder agreement and a new design, so done would require an agreed approach, documented developer experience, and follow-up implementation and tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
frontend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.