aspnetcore: Blazor Two Way Binding Error
Describe the bug
A component with a parameter called say Value manages 2 way binding with an accompanying ValueChanged event handler. If (i) this event handler is called from within the setter for Value and (ii) the component attaches to a cascading value, the component’s ability to set the value is neutralized by the value bouncing back to its original state.
For what it’s worth I believe that I have seen repetitive 2 way binding bounce in the past but cannot reproduce that.
To Reproduce
Please fork https://github.com/simonziegler/TwoWayBindingDemo! The demonstration is in the index page. You will find two components each of which is referenced both within and outside a cascading value. The one that fails has its result shown in red. At the bottom of the page you’ll see a list of the bound value changes captured by the parent - you’ll see a bounce on the offending version.
Exceptions (if any)
n/a
Further technical details
- ASP.NET Core version: both .NET 3.1 and .NET 5
- Include the output of
dotnet --info
.NET SDK (reflecting any global.json):
Version: 5.0.100-preview.7.20366.6
Commit: 0684df3a5b
Runtime Environment:
OS Name: Windows
OS Version: 10.0.19041
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\5.0.100-preview.7.20366.6\
Host (useful for support):
Version: 5.0.0-preview.7.20364.11
Commit: 53976d38b1
.NET SDKs installed:
3.1.201 [C:\Program Files\dotnet\sdk]
3.1.302 [C:\Program Files\dotnet\sdk]
3.1.400-preview-015203 [C:\Program Files\dotnet\sdk]
5.0.100-preview.7.20366.6 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.0-preview.7.20365.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.0-preview.7.20364.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.0-preview.7.20366.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download
- The IDE (VS / VS Code/ VS4Mac) you’re running on, and it’s version

About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 16 (5 by maintainers)
Commits related to this issue
- implement feedback from dotnet/aspnetcore#24599, which required a redesign of events. Therefore, we have new major version — committed to excubo-ag/Blazor.Grids by deleted user 4 years ago
Watching the community stand up just now @danroth27 said to point out what needs attention in issues. This is one!
I’ve played around with the code and added some
Console.WriteLine. This reveals the following outputs:For Component1 without cascading value:
And with cascading value it becomes
The diff is therefore:
This to me shows the bug quite clearly: The parent receives the correct value, yet the child component is subsequently updated to the original value again.
The way I see it, the loop back to setting values in the component is unavoidable. However, it shouldn’t reset any values. The order of things should be
if (backing_field == value) { return; }in the setter of the property.In conclusion, the following code should never fail:
This can’t result in an infinite loop, because the condition in the setter breaks it, under the assumption that the value that is bound in the parent component is updated to the new value before the re-render and the re-setting of the parameters happens.
OK, I’ve looked in more detail and see what’s going on. There’s an explanation below, but before getting to that, I’ll restate my claim that the core problem is the child component overwriting its own
[Parameter]property value. By avoiding doing that, you can avoid this problem.In this instance, and in others, Blazor relies on parent-to-child parameter assignment being safe (not overwriting info you want to keep) and not having side-effects such as triggering more renders (because then you could be in an infinite loop). We should probably strengthen the guidance in docs not just to say “don’t write to your own parameters” and expand it to caution against having side-effects in such property setters. Blazor’s use of C# properties to represent the communication channel from parent to child components is pretty convenient for developers and looks good in source code, but the capacity for side-effects is problematic. We already have a compile-time analyzer that warns if you don’t have the right accessibility modifiers on
[Parameter]properties - maybe we should extend it to give warnings/errors if the parameter is anything other than a simple{ get; set; }auto property.So in this case the solution is pretty simple: replace
Component1’sValueproperty with a simple{ get; set; }one, and the instead of trying to write to it, haveComponent1respond to button clicks by triggeringValueChanged. Then you don’t have any recursive render cycle, and no data overwriting occurs.Behind the scenes
The problem you observe is because, when there’s a
<CascadingValue>, there are two different sources of parameters for the child:<CascadingValue>componentWhen a rendering cycle occurs, one of these has to supply values before the other, and in your case it happens to be the
<CascadingValue>. For consistency, whenCascadingValuesupplies updated parameters to a subscriber, the subscribee receives not only the cascaded parameter value but also a snapshot of whatever previous set of parameters it received. This is so that you don’t have to worry about implementing any special-case logic to allow for parameters being missing. In the event that you’re modifying a parameter value during the same cycle as a cascaded value is propagating, the parameter snapshot will still contain the previous value. Normally this causes no problems because, as long as setting the parameter values is not side-effecting, the renderer will continue and also supply the updated values from the parent that’s re-rendering, and so you end up with a consistent and correct set of final values. The problem only occurs because setting the parameter values in the repro case does have side-effects which include triggering another re-render on the parent. The solution therefore is to leave the parameter communication channel alone (i.e., don’t write to the properties, nor put insetlogic that has side-effects).I hope all this makes sense. If you think I’m still missing something, please let me know. The actions I plan to take on this are:
[Parameter]property isn’t just{ get; set; }or if it’s written to from inside the component@stefanloerwald added a separate page (from the menu) with his logging output and this is merged into the repo. Please run the solution without IIS Express and you’ll see his logging results in the command window that shows the results of
dotnet runI too have experienced this issue and have spent endless hours trying to understand. If this isn’t considered a bug it will require some excellent documentation as the behavior is quite contrary to what one would expect. I hardly expect this sequence:
if I change a parameter value in a component
signal a value change to the parent
then the parent first sets the parameter to the old value
Maybe the key to this is in this line ? “…under the assumption that the value that is bound in the parent component is updated to the new value before the re-render and the re-setting of the parameters happens”
I mean I don’t know for sure, but I do know it felt really bad to be doing it that way - and changing it the way I did superficially confirmed it as a reasonable concern - because it works the other way.
I would also note that I would never “double-bind” like this example (where the incoming parameter is then bound inside the component) anyway.
I always bind the backing field to the html element in my component - as in my example - and so I have never had this problem.
So - TLDR; - The reason I didn’t personally add my example here is because I am just another user and didn’t want to sidetrack this issue with what could be considered a workaround.