-
Notifications
You must be signed in to change notification settings - Fork 10k
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
EventCallback: Updates to Event Handling and Binding #6351
Labels
area-blazor
Includes: Blazor, Razor Components
Done
This issue has been fixed
enhancement
This issue represents an ask for new feature or an enhancement to an existing one
Milestone
Comments
rynowak
added
Needs: Design
This issue requires design work before implementating.
area-mvc
Includes: MVC, Actions and Controllers, Localization, CORS, most templates
labels
Jan 3, 2019
Yes. |
rynowak
added
2 - Working
and removed
Needs: Design
This issue requires design work before implementating.
labels
Feb 15, 2019
rynowak
changed the title
Design Review: Binding
EventCallback: Updates to Event Handling and Binding
Feb 23, 2019
danroth27
added
the
enhancement
This issue represents an ask for new feature or an enhancement to an existing one
label
Feb 25, 2019
This was referenced Mar 8, 2019
mkArtakMSFT
removed
area-mvc
Includes: MVC, Actions and Controllers, Localization, CORS, most templates
labels
May 9, 2019
ghost
locked as resolved and limited conversation to collaborators
Dec 3, 2019
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
area-blazor
Includes: Blazor, Razor Components
Done
This issue has been fixed
enhancement
This issue represents an ask for new feature or an enhancement to an existing one
Summary
In ASP.NET Core 3.0.0-preview3/Blazor-0.9 we're making a big set of changes to how event routing works in Components. Powering this is a new feature called
EventCallback
that every Components user will want to learn about. If you've ever had to add a manualStatehasChanged
to get rendering to occur properly then these are changes you will probably be interested in.What's changing
Event Handlers and
bind-...
are having their event routing improved to be more intuitive. Consider the following components:If you've been using Components so far, you would expect that when you click the button, the
MyButton
component will re-render after theUsesMyButton.ShowSecretMessage
method is called. We call this behaviour event routing. When theonclick
event fires in the browser, the rendering process will notify theMyButton
component that an event occured, and (by default)MyButton
will re-render.UsesMyButton
does not re-render because it was never notified about the event. The correct way to describe event routing in previous releases is to say that events are routed to the component that registered them.If you wanted
UsesMyButton
to also re-render then you would have to insert a manual call toStateHasChanged()
. Here's the corrected method:There are a few more common cases where this could occur, but they can all be generalized under the statement:
Well not anymore.
We're making some changes that are going to make this just work for all of the common cases. Instead of routing events to the component that registered the event, we are going to route them based on the delegate target.
This seems kind of esoteric at first, but we can simplify it a bit. Delegates (lambdas, converted from methods) all have a
.Target
property.The official docs describe it as:
So if you pass an instance method (like
ShowSecretMessage
) it means that the event will be now routed toUsesButtonComponent
. This is inuitive because the whole purpose of an event handler is change the state of something. Now the event will be routed to the component whose state changed. With the updated event routing behavior the previous example will just work.What about a lambda? Well lambdas have a
.Target
as well. Usually what happens in a lamdba that changes a component's state is that you will close over the current component. The compiler adds a hidden instance method to the component class, and when the delegate object is created, its.Target
will refer to the component. There are some cases where this doesn't happen, and that will be explained further later.So we could also write
UsesMyButton
like the following, and it will just work with the new event routing.Another problem
However if you wrote
UsesMyButton
like the following, you'd have a problem.Now the lambda is a full closure because it closes over the
message
variable. The.Target
property won't be theMyButton
but will instead by an instance of a generated class. So when the button is clicked, there's nowhere to route the event.We have a solution for this too.
Introducing
EventCallback
... We're adding a new basic building block to the Components programming model.EventCallback
is a new value type that wraps a delegate and attempts to smooth over several of the problems that are common using delegates between components.Here's the same sample with an
EventCallback<UIMouseEventArgs>
.Notice the only code change here is that we changed
Action<UIMouseEventArgs>
toEventCallback<UIMouseEventArgs>
. However, we've now solved the problem with event routing. The compiler has built-in support for converting a delegate to anEventCallback
, and will do some other things to make sure that the rendering process has enough information to dispatch the event properly.The generated code for this looks like:
So the
EventCallback
is always created with a reference to the component that created it. We also do some optimizations to try and limit the amount of allocations this creates (EventCallback
is a struct, but will be boxed in some cases).From the point of view of
MyButton
- it gets a value ofEventCallback<>
and passes that off to theonclick
event handler. Event handlers in Components now understandEventCallback<>
as well as delegates. The experience for usingEventCallback
should be smooth at this point. You can use it in place of anAction
for event handlers andbind-...
.The event handling infrastructue still understands delegates. So if you wrote C# code that uses the
RenderTreeBuilder
directly, it will still work. Using C# to program against new component code that exposesEventCallback
as a parameter will need to useEventCallback.Factory
to create values.Adapting delegate types
Another feature of
EventCallback
is that it provides much better support for flexibility with delegates than we had before. RememberMyButton
?The author of
MyButton
made a choice thatOnClick
has to be a synchronous method that accepts aUIMouseEventArgs
as an argument. Using basic delegates it's not possible to assign aFunc<UIMouseEventArgs, Task>
or anAction
to this because the types are incompatible. If you need to do async work in this situation, your only option isasync void
.We don't like
async void
as much as an option because it requires manual error handling - and we've tried to make error handling more predictable as well during this release.Fortunately, using
EventCallback<T>
allows us to solve this problem as well. The set of overloads onEventCallback.Factory
enables to convert a bunch of different delegate types to work transparently, and the compiler is smart enough to callEventCallback.Factory
for you in a.razor
file.Doing the following will now just work - with proper error handling.
Invoking EventCallback manually
You can invoke an
EventCallback
like:await callback.InvokeAsync(arg)
.Yes,
EventCallback
andEventCallback<>
are async. We think that this will work well, because it's now much easier to use async thanks to the other improvements we made in this area.EventCallback<>
is strongly typed and requires a specific argument type to invoke.EventCallback
is weakly-typed and will allow any argument type.TLDR Migration Guidance
We recommend using
EventCallback
andEventCallback<T>
when you define component parameters for event handling and binding. The only place you can't use these new types is for child content - those should still beRenderFragment
orRenderFragment<>
.Prefer
EventCallback<>
where possible because it's strongly typed and will provide better feedback to users of your component. UseEventCallback
when there's no value you will pass to the callback.Remember, consumers don't have to write different code when using a component because of
EventCallback
. It should be very un-impactful to adopt this, and hopefully you find that it resolves a few thorny problems and makes the experience better for you. As with all things we welcome your feedback, especially code samples of things that are hard or don't work well.Known issues
One known issue is that we had to weaken the compiler's enforcement of strong-typing in this release. In particular, it should be possible to assign a delegate value in an attribute to just about any parameter. These cases are mostly non-sensical, and they will compile but fail at runtime.
We unfortunately had to do this because 3.0-preview3 does not line up with a Visual Studio release - Visual Studio 2019 is not public yet but is in a stabilization period and we will not be able to add new features until the next release. The problem with this exactly is that the editor sees all of the new types, but has the old code generation with no knowledge of
EventCallback
. There were a few cases where a common usage of this new feature would cause spurious errors in the editor. We had to scramble to workaround this problem by making a change to the runtime, resulting in weaker type-checking.This is a fairly major compiler feature for us to without a corresponding tools update, so we are very excited to bring it to you!
The text was updated successfully, but these errors were encountered: