-
Notifications
You must be signed in to change notification settings - Fork 10.3k
For two-way bindings, enforce consistency between .NET model and DOM by patching old tree. Fixes #8204 #11438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
For two-way bindings, enforce consistency between .NET model and DOM by patching old tree. Fixes #8204 #11438
Conversation
5547c3c
to
57ff22d
Compare
This comment was made automatically. If there is a problem contact [email protected]. I've triaged the above build. I've created/commented on the following issue(s) |
57ff22d
to
fbffba2
Compare
@SteveSandersonMS I see you force pushed the branch and no dependency updates remain. Do you still need anything from me here? |
@dougbu No, nothing else needed. Unless you’re part of the BRT or whatever the current equivalent to that is, because there is still https://github.com/aspnet/AspNetCore-Internal/issues/2707 |
fbffba2
to
6d88845
Compare
{ | ||
// We only allow the client to supply string or bool currently, since those are the only kinds of | ||
// values we output on attributes that go to the client | ||
if (!(newFieldValue is string || newFieldValue is bool)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which case can this not be either an int or bool, wouldn't that signal that something went terribly wrong? (Like could a number get in here somehow?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
I'm not aware of any valid way that the value here might be something other than string
/bool
. If it was, we could just throw. Maybe that would be clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at your tests, it's fine if this simply returns as the value comes from the client and is untrusted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might help a bit - #11517
/// <returns> | ||
/// A <see cref="Task"/> which will complete once all asynchronous processing related to the event | ||
/// has completed. | ||
/// </returns> | ||
public virtual Task DispatchEventAsync(int eventHandlerId, UIEventArgs eventArgs) | ||
public virtual Task DispatchEventAsync(int eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the expectation that this chain grows bigger than 1 when there are rapidly firing events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The chain of replaced event handler IDs? If so, yes, it can grow beyond 1.
Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId); | ||
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this for example be a case where there are multiple events fired with the old event handler (like typing fast or moving the cursor quick) and the render batches have not been produced yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great set of changes.
I didn't get to follow all the details because its very low-level and likely requires some debugging to understand it fully, but the changes look solid and the test coverage is appropriate.
Great job!
6d88845
to
1335f41
Compare
{ | ||
public EventFieldInfo() { } | ||
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } | ||
public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #11517 in case we have interest in doing the work here.
// to process invalid input but avoid dirtying the state of the component if can't be converted. Imagine if | ||
// we assigned default(T) on failure - this would result in trouncing the user's typed in value. | ||
// We only invoke the setter if the conversion didn't throw, or if the newly-entered value is empty. | ||
// If the user entered some non-empty value we couldn't parse, we leave the state of the .NET field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
{ | ||
// We only allow the client to supply string or bool currently, since those are the only kinds of | ||
// values we output on attributes that go to the client | ||
if (!(newFieldValue is string || newFieldValue is bool)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might help a bit - #11517
This is the rather nontrivial solution to a multitude of related async-binding and diff-inconsistency issues. As of this change, two-way bindings have a stricter behavior whereby they:
In most ways this is purely advantageous. Various cases where the behavior was bizarre or broken before are now fixed (e.g., binding a checkbox to a property whose value is fixed as
true
orfalse
before didn't reflect that in the UI - now the checkbox will forcibly revert itself to "checked" or "unchecked" as appropriate"). Also it's much safer to revert invalid edits to number fields, because now the user can see exactly how their input is being interpreted - previously they had no way to know if their edit was being ignored because it was considered invalid.However the extra strictness will make developers go through extra hoops in a few cases. The most notable one is textboxes bound to non-nullable
int
/double/
etc that usebind-value:event="oninput"
. These will no longer allow unparseable values such as being empty or just containing-
, so developers will have to either:bind-value:event="oninput"
for non-nullable numbers, since you don't want to prevent the invalid intermediate states. Just use the default change event which is "change".string
so you can do whatever you want with the invalid intermediate states<InputNumber>
which already handles this nicely. In the future we might extend that to supportbind-value:event="oninput"
, and it will do what you expect because behind the scenes it's binding to astring
and handling parsing separately so it can record validation errors.