Support @bind:get, @bind:set, @bind:after#70
Conversation
| } | ||
| } | ||
|
|
||
| private static string IsAwaitable(IPropertySymbol prop) |
There was a problem hiding this comment.
I have to admit I don't really have any sense of what this is for. Could you briefly summarise what the IPropertySymbol values here represent (e.g., where they come from) and what difference it makes whether we want to regard them as awaitable or not?
There was a problem hiding this comment.
We need for component bindings in the form of Value and ValueChanged where ValueChanged can be Action<T> or Func<T, Task>.
Unlike for other bindings when we can always create an EventCallback, in this case we can't (in the synchronous case at least). So what this method does is, when we are inspecting the ValueChanged property, (which we already know is a delegate) we check to see if it has a return type, and if that return type is awaitable, which covers Task, ValueTask, and any other potential awaitable in the future.
The logic in this method is the same that the roslyn compiler uses to make that determination.
We use this information during the bindloweringphase to generate either an Task returning callback or a void returning callback
| public const string TypeCheck = "global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck"; | ||
| public const string CreateInferredEventCallback = "global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback"; | ||
| public const string InvokeSynchronousDelegate = "global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeSynchronousDelegate"; | ||
| public const string InvokeAsynchronousDelegate = "global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate"; |
There was a problem hiding this comment.
I'd be interested to see how these helpers will look (e.g., their range of overloads). Does that info exist anywhere yet?
There was a problem hiding this comment.
| IntermediateToken - - CSharp - global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => ParentValue = __value, ParentValue) | ||
| IntermediateToken - - CSharp - global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, | ||
| IntermediateToken - - CSharp - __value => ParentValue = __value | ||
| IntermediateToken - - CSharp - , ParentValue) |
There was a problem hiding this comment.
I'm not sure if breaking this over multiple lines is for an important reason, but it does add a lot of noise to the baseline diffs.
Is there any chance you could put this part of the code formatting back to how it was before, to minimize baseline churn, and then maybe do a separate PR (or at least an independent commit for it at the end of this PR after the code review is done)?
There was a problem hiding this comment.
It's quite painful to do this, instead I would suggest the following:
- Clone the PR:
- run
git diff origin/main --diff-filter=M -- 'src/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/**'to isolate these changes - run
git diff origin/main --diff-filter=A -- 'src/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/**'to only look at new baselines
- run
Yep, it is a bit of a challenge, but I think we can handle it. The main issue is just trying to keep the baseline diffs to a human-readable level. I suggest avoiding the existing test case name changes until right at the end when everything is done and signed-off, and similarly revert the change that splits up the generated Likewise, if you could avoid doing any squash-rebasing from here on, it will make it possible just to review the subsequent commits and not have to go back over the existing changes. |
|
So far, this looks like a great enhancement! |
99bef6d to
1b7302a
Compare
|
🆙 📅 |
115b37a to
3c4b8e7
Compare
|
@SteveSandersonMS is there any concrete feedback on the PR you want us to discuss or are we good to move forward? |
…nd:get and bind:set style
…ntDiagnosticFactory.cs Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
96e14b5 to
eaab3d6
Compare
|
Is there any article that explains a bit more? Don't we need to call ValueChanged anymore, or we will call in after method? |
|
@mckaragoz Please have a look at ASP.NET Core Blazor data binding |
Adds support for bind:get, bind:set, bind:after
This PR introduces a new syntax for bind that allows specifying an action to run after the binding or specifying the setter for the binding directly.
There are two companion PRs to this one:
There are in general three scenarios to cover:
<input type=text />.When binding to components the receiving property can either be an
Action<T>, Func<T,Task>, Func<T,ValueTask>or anEventCallback<T>and we need to generate different code for each one of them.When binding to elements we rely on new overloads of CreateBinder that take in an
EventCallback<T>with the appropriate T for the elements that we know how to bind to in addition to the existing overloads that receive anAction<T>. For example, for T = bool?:Within the compiler we take care of creating the EventCallback using a CreateInferredEventCallback helper when the developer uses
bind:setorbind:after. For example:and
For components we also have to deal with the cases where the component property is an
Action<T>or aFunc<T,<<awaitable>>>where<<awaitable>>follows the same rules as the C# compiler for determining if usingawaitis supported and coversTask,ValueTask, and any future addition. In these cases we can't leverageCreateInferredCallbackand instead we rely on two helper methodsInvokeSynchronousDelegateandInvokeAsynchronousDelegatethat are provided by the runtime. These two methods help us make possible to invoke the provided expression by the developer no matter whether it is a method group or a lambda and take care of producing an error when the developer tries to bind to an Action with a function returning an awaitable result.For example, for:
we generate code like the one below: