Open garrettlondon1 opened 4 months ago
Thanks for reaching out, @garrettlondon1.
It looks like the reason the error boundary doesn't show is because the error boundary is being rendered as a child component of the component throwing the exception. Since exceptions only bubble up the component hierarchy, a child error boundary component won't catch that exception.
This behavior could be changed, but we're going to backlog this for now to collect more feedback.
It looks like the reason the error boundary doesn't show is because the error boundary is being rendered as a child component of the component throwing the exception.
Thanks @MackinnonBuck, but I'm confused.
Counter.razor code block has the method IncrementCount Counter.razor markup has the onclick handler that calls IncrementCount
This interactivity lives within the same exact component, no other components are throwing an exception. Why would an error boundary not work on the Counter.razor page?
What am I missing here?
And what is the solution to catch these unhandled exceptions without putting a try catch block in every single point of interactivity (which is crazy at scale)
Hello guys,
I've got two examples: 1) This example contains case when error is caught. There are 2 components: 1st rises error and seconds wraps 1st with ErrorBoundary
ComponentWithError.razor
WrappingComponent.razor
2) This example contains case when error is not caugh and unhandled exception happens.
I found the reson why errorboundary does not work when error happens just inside content but not in child component There is such a class .Microsoft.AspNetCore.Components.RenderTree.Renderer.cs which contains HandleExceptionViaErrorBoundary method that recursively tries to find ErrorBoundary to handle exception. But search goes in tree by components.
That's why my first case works. Because WrappingComponent.razor contains ErrorBoundary But second case has no wrapping component or that component just does not have ErrorBoundary . ErrorBoundary is located just inside that component. And that case is not covered by that loop in Renderer.cs
Seems like we are obliged always to wrap components with ErrorBoundary but cannot just wrap some content.
Any reason for that ??? Why whole tree cannot be checked ??
@MackinnonBuck @danroth27 I see three options that Blazor forces developers into:
Use global interactivity with Error Boundary > not an option, cannot take advantage of SSR and per-page interactivity
Wrap every interactive component inside another page entirely so that Renderer.cs can see ErrorBoundary > terrible design
Use a try catch block to handle exceptions in every single interactive method > not scalable at all
Thank you @IevgeniiKunshchykov
I believe @IevgeniiKunshchykov's assessment matches are our understanding of the issue. We could make a change to make it possible for error boundaries to handle exceptions thrown by HTML event handlers. This would be a behavior change that could potentially be breaking, but it seems likely that the impact could be negligible. But before we make such a change, we want to make sure we have sufficient justification and user demand for the change. We don't want to risk breaking existing users if the beneficial impact is unclear.
@garrettlondon1 If we were to make this change, are you saying that error boundaries would then meet your error handling needs? Or are you saying in your last comment that you think Blazor needs an entirely different error handling solution?
@danroth27 thanks for the quick reply..
ErrorBoundary is definitely the proper solution for Interactive blazor error handling
The problem is that you have to wrap every single interactive component in it because App.razor/Router is now static
All this story is asking for, is to make every interactive action inside of the ErrorBoundary able to bubble up the component tree correctly, and hit the ErrorBoundary properly: not throw an unhandled exception
BTW ... I think we already cover that the error boundary should be around the portion of the component hierarchy that can throw an exception. We don't say anywhere that it can directly wrap content. Mackinnon and I spent some time working on the coverage, and we're taking feedback, but I think what's being discussed here is covered.
UPDATE (7/18): After discussion with @TimMurphy and @MackinnonBuck, I made improvements to our coverage to at least help clear up confusion. The updated section is live now at ...
https://learn.microsoft.com/aspnet/core/blazor/fundamentals/handle-errors#error-boundaries
Thanks @guardrex, but the example used in the docs is a very poor example, especially when it relates to this issue.
In this photo, why would you ever make the Home component Interactive?
If the Home component was not interactive (which it should NOT be), and the Counter component was interactive. Then the error boundary in the docs would not work. EVEN if the Counter is wrapped in an ErrorBoundary also
Why are we letting every individual developer figure this out painfully on their own when migrating to SSR, instead of either clarifying the docs or fixing the underlying issue?
I would add to the docs an example where:
Home.razor is static, does not wrap EmbeddedCounter in an ErrorBoundary
EmbeddedCounter is Interactive, wrapped in an ErrorBoundary
Explain, why this does not work with @onclick handlers Explain, why this does work if the event originates from another subcomponent
I don't agree with your feedback (yet 😄), so I yield to @MackinnonBuck and @danroth27 to determine if further changes are needed. If not, we'll continue to take feedback and see if others find it confusing.
Most of what you're discussing is covered prior to that example. For analysis, @MackinnonBuck and @danroth27 should see the full section ...
I can confirm @guardrex that we also find it confusing. We are looking for a reliable way to log Exceptions from a standalone Blazor Web Assembly Application to a Server within a Production Environment. Sources indicate that using the OnErrorAsync Method of the ErrorBoundary Component as a "global exception handler" is a way of achieving this. On closer inspection, however, it seems that Exceptions thrown from an HTML OnClick Event are not reliably handled by the ErrorBoundary Component. For other Developers looking to do something similar then it may be helpful for Microsoft to add a Note to this effect to their Documentation to hopefully minimise wasted effort going forwards. Having given this considerable thought we think the best way forwards for us, until Microsoft comes up with a better solution, is to remove the ErrorBoundary Component from our Application. @danroth27 in response to your Question to @garrettlondon1 ideally we would like a Component that provides a reliable way of handling Exceptions globally rather than adding the same code multiple times to Try...Catch blocks, which seems to be the only solution as it stands.
@cwoodward-taurus ... I'm going to enhance the coverage with additional guidance :point_up:. However, I don't have product unit remarks yet on the main question that you're asking about outside of just remarking that ErrorBoundary no-ops on them ...
Exceptions thrown from an HTML OnClick Event are not reliably handled by the ErrorBoundary Component
Article remarks for that scenario will need to come from the product unit engineers here. I'll include anything else they want to say about it when I work that issue, and I should reach that issue next week after we get back from the Labor Day holiday.
@cwoodward-taurus I want to note there is a solution. You need to create another wrapper on top of every interactive component you have..
@IevgeniiKunshchykov example calls it WrappingComponent
Page.razor (static)
Its incredibly painful, but it works for all scenarios. Now that I'm thinking.. I wonder if you can make an interactive component that wraps interactive ChildContent.
Almost, wrapping ErrorBoundary itself inside of another interactive component which takes a ChildContent parameter. Will try it out.
I am using blazor interactive components in MVC project and the fact that I have to wrap every component I create in another component just to catch errors is a bit frustrating, to say the least. Or is there another way to globally catch errors in such a case?
Can we get this pulled out of the backlog?
Here is one more case with unexpected behavior of errorboundary which is crucial for us. We've solved the problem mentioned above with wrappers around our components. Here is example:
But when internal component has some state, like here I have counter with some value:
And when exception is thrown and caught by errorboundary then internal component of errorboundary is just recreated and state is lost:
Can this be solved somehow? Because it seems each approach of using errorboudary has some holes :(
@danroth27 there doesn't look like there is currently a good solution to ErrorBoundary... regarding above comment.
Even if the error boundary keeps child content in the view, and does not use custom ErrorContent RenderFragment, all state will be cleared on error with WrappingComponent approach.
It would be nice to be able to include the ErrorBoundary
within the component I want to handle itself. Now, as others pointed out, I have to hoist up the ErrorBoundary
a level instead of having a nicely encapsulated component.
Quite frankly, the inflexibility of it makes it useless for most scenarios where I would have wanted to use it.
To elaborate further. Here's a real scenario where this ErrorBoundary
does work:
<LoggingErrorBoundary>
<ChildContent>
<ServiceStatusTile Service="@svc"/>
</ChildContent>
<ErrorContent>
<div class="bg-red-500 text-white p-3 rounded-lg">
<h3 class="font-bold">An error occurred</h3>
</div>
</ErrorContent>
</LoggingErrorBoundary>
Fine, this renders the error content. However, it totally wipes out the component:
Compare this to the happy path, where the components all render:
Now, our ServiceStatusTile
is a single component. What if we'd want to have an error boundary in the component itself and potentially render different parts of the component still when there's an error? We can't wrap arbitrary code, so we can't do that without these component wrappers that @garrettlondon1 mentioned. Huge pain, not maintainable.
Is there an existing issue for this?
Describe the bug
When creating a new Blazor Web App template and using InteractiveServer components without global interactivity.. I cannot get the error boundary to show
In a static component with an interactive island, it does not show Error content.. in a interactive page, it doesnt show error content either..
Confused what I am doing wrong
Expected Behavior
Error boundary shows child content or error content when an unhandled exception happens in a static page or interactive page/component
Created from Blazor Web app
Steps To Reproduce
Go to the home page, and click the button to increase the count
Exceptions (if any)
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100] Unhandled exception rendering component: Exception of type 'System.Exception' was thrown. System.Exception: Exception of type 'System.Exception' was thrown. at StaticErrorBoundary.Components.Pages.Counter.IncrementCount() in /Users/garrettlondon/Github/StaticErrorBoundary/Components/Pages/Counter.razor:line 29 at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync[T](MulticastDelegate delegate, T arg) at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, Object arg) at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs, Boolean waitForQuiescence) fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit 'XOj9HyZGUCrv2y5bZvv5Mswe399H9w8ayWexTB5H--w'. System.Exception: Exception of type 'System.Exception' was thrown. at StaticErrorBoundary.Components.Pages.Counter.IncrementCount() in /Users/garrettlondon/Github/StaticErrorBoundary/Components/Pages/Counter.razor:line 29 at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync[T](MulticastDelegate delegate, T arg) at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, Object arg) at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs, Boolean waitForQuiescence)
.NET Version
8.0.302
Anything else?
No response