-
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
AsNonRenderingEventHandler + ErrorBoundary = unexpected behavior #54543
Comments
Added minimal reproduction project to "Steps To Reproduce" without MudBlazor. |
@mkArtakMSFT @SteveSandersonMS This is disruptive because Blazor has a big visual bug during navigation (#53863) caused by extra renders, but marking it non-rendering isn't compatible with |
Another thing I tried: public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(Func<TValue, Task> callback)
{
var receiver = new AsyncReceiver<TValue>(callback);
return value =>
{
var callbackWorkItem = new EventCallbackWorkItem(receiver.Invoke);
return ((IHandleEvent)receiver).HandleEventAsync(callbackWorkItem, value);
};
} The exception flows, but the re-render occurs. (and not like it's possible to use this for the Action versions) Then I tried just a plain class: public class NoRender<TValue> : IHandleEvent
{
private readonly Func<TValue, Task> _callback;
public NoRender(Func<TValue, Task> callback)
{
_callback = callback;
}
public Task Invoke(TValue arg) => _callback(arg);
public Task HandleEventAsync(EventCallbackWorkItem item, object? arg) => item.InvokeAsync(arg);
}
@onclick="new NoRender<MouseEventArgs>(OnClickHandler).Invoke" But the exception doesn't flow. I also tried to play with This is very broken, and I give up on trying to find the solution myself. Only implementing |
Thanks for reaching out, @ScarletKuro. When an event handler is created from a delegate, the event's receiver is the delegate's An event's receiver is used within the framework when determining how to handle exceptions thrown from within that event. If the receiver is an However, another way to dispatch an exception to an error boundary is via Here's an example that achieves this by defining a custom base component type: public abstract class CustomComponent : ComponentBase
{
private protected Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(this, callback).Invoke;
private protected Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(this, callback).Invoke;
private protected Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(this, callback).Invoke;
private protected Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(this, callback).Invoke;
private sealed class SyncReceiver(CustomComponent component, Action callback) : ReceiverBase(component)
{
public void Invoke() => callback();
}
private sealed class SyncReceiver<T>(CustomComponent component, Action<T> callback) : ReceiverBase(component)
{
public void Invoke(T arg) => callback(arg);
}
private sealed class AsyncReceiver(CustomComponent component, Func<Task> callback) : ReceiverBase(component)
{
public Task Invoke() => callback();
}
private sealed class AsyncReceiver<T>(CustomComponent component, Func<T, Task> callback) : ReceiverBase(component)
{
public Task Invoke(T arg) => callback(arg);
}
private abstract class ReceiverBase(CustomComponent component) : IHandleEvent
{
public async Task HandleEventAsync(EventCallbackWorkItem item, object arg)
{
try
{
await item.InvokeAsync(arg);
}
catch (Exception ex)
{
await component.DispatchExceptionAsync(ex);
}
}
}
} ...but you could also achieve this by following the Hope this answers your questions! Please let us know if you think there's a framework issue that needs to be addressed. If not, we'll close this out. |
@MackinnonBuck thanks. Your code seems to work, I didn't find any issues with it. The exception flows to the Also, it would be helpful if the documentation at https://learn.microsoft.com/en-us/aspnet/core/blazor/performance?view=aspnetcore-8.0#avoid-rerendering-after-handling-events-without-state-changes could be updated to mention that the current implementation doesn't flow exceptions. If developers want this behavior, they should call |
Thanks @ScarletKuro, glad to hear it works. Yeah, unfortunately these APIs didn't exist in .NET 7. There might be a creative way to reimplement @guardrex, could we add a note in the docs that the example doesn't flow exceptions through to error boundaries, but you can call |
Docs were updated, thanks everyone. |
Is there an existing issue for this?
Describe the bug
Hello.
In MudBlazor we recently did this change: https://github.com/MudBlazor/MudBlazor/pull/8203/files#diff-7258903903d30bdc973979ca0d64c61adbee3f4441e47749100907f522060a47
We added
AsNonRenderingEventHandler
(that was copied from official MS document) to our internalonclick
and now theErrorBoundary
doesn't catch the exceptions that are fired within theAsNonRenderingEventHandler
event:You will see in console:
rather than the
ErrorContent
.The weird thing about it if that I use alternative to
AsNonRenderingEventHandler
:Then everything will work as expected and
ErrorBoundary
is workinganother weird thing is that if I change this:
to this:
Then the
ErrorBoundary
will catch the exception as well, but the problem is that thisreturn async value => await receiver.Invoke(value);
and thisreturn new AsyncReceiver<TValue>(callback).Invoke
should be EQUIVALENT in C# (except that for first one an asyncstatemachine is created, but end result should be the same and no side effects).upd: well, with this snippet https://try.mudblazor.com/snippet/cOQSERlIheRAyAJe I just found out that
async value => await receiver.Invoke(value)
doesn't prevents re-rendering, but theIHandleEvent
implementation in component works, so this doesn't explain few moments:EventUtil
from docs theErrorBoundary
doesn't work.IHandleEvent
in the component it works great - the exception flows and no re-render is happening, but theEventUtil
in 1. or 2. doesn't work as I expect?I described the same in this in MudBlazor issue: MudBlazor/MudBlazor#8365 (comment)
@SteveSandersonMS could you please explain, as I'm very confused on what's going on.
Expected Behavior
ErrorBoundary
to work withAsNonRenderingEventHandler
Steps To Reproduce
This would require to fork the https://github.com/MudBlazor/MudBlazor repository and play with the MudButton.razor and MudButtonBase and the snippet from https://try.mudblazor.com/snippet/caQSaxvSoRhvMaSP(added very minimal repro below, so not relevant anymore)upd: Minimal reproduction without MudBlazor:
BlazorEventUtilsIssue.zip
Exceptions (if any)
.NET Version
8.0.103
Anything else?
No response
The text was updated successfully, but these errors were encountered: