ErrorBoundary

Hello on my Mainlayout page i'm using the ErrorBoundary component.
This works well for all the page's.

But is there away to do the same for all the page's im opening with the
await DialogService.OpenAsync
service ?
So can i apply a kind of Layout to all these modal page's ?

Regards Bart

Hi @schuttea,

Components used in dialogs do not use layouts. Only routable pages rendered via navigation can have a layout. You can add an ErrorBoundary to the component that you open with DialogService.OpenAsync

Ok thx,
unfortunately i use these components al lot. So to put them on each component is a bit of work :slight_smile:
Regards.

Hi

I have a follow on question on this as something may now be possible for the dialogs.

I found this in the Telerik support.

They have a root component that can be wrapped in ErrorBoundary. Is there an equivalent in Radzen?

I tried wrapping RadzenComponents tag that is in MainLayout but that didn't work.

It tested the same and it seems to work:

  1. Wrap <RadzenComponents /> with ErrorBoundary in MainLayout.razor:
     <ErrorBoundary>
       <RadzenComponents />
     </ErrorBoundary>
    
  2. Add a button that throws exception in the dialog page
    <RadzenButton Text="Throw" 
      Click="@(args => throw new Exception("This is a test exception"))" />   
    
  3. Clicking the button shows the ErrorBoundary: error-boundary
    It also logs the following in the console:
     warn: Microsoft.AspNetCore.Components.Web.ErrorBoundary[100]
      Unhandled exception rendering component: This is a test exception
      System.Exception: This is a test exception
         at RadzenBlazorDemos.Pages.DialogCardPage.<>c.<BuildRenderTree>b__0_0(MouseEventArgs args) in /Users/korchev/github/radzen-blazor/RadzenBlazorDemos/Pages/DialogCardPage.razor:line 10
         at InvokeStub_Func`2.Invoke(Object, Span`1)
         at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Radzen.Blazor.RadzenButton.OnClick(MouseEventArgs args) in /Users/korchev/github/radzen-blazor/Radzen.Blazor/RadzenButton.razor.cs:line 154
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
    
  4. If no error boundary is present the default UI exception is shown: no-error-boundary

Thank you for your reply.

I created new app and made it simple and wrapped @body & RadzenComponent with an ErrorBoundary with ChildContent and ErrorContent like so

<ErrorBoundary>
    <ChildContent>
        <RadzenComponents />
    </ChildContent>
    <ErrorContent>
        <h2>An error occurred</h2>>
    </ErrorContent>
</ErrorBoundary>

This worked so I agree with you that wrapping around RadzenComponent does work.

However, what I was doing was as follows

<ErrorBoundary>
    <ChildContent>
        <RadzenComponents />
    </ChildContent>
    <ErrorContent>
        <DisplayError />>
    </ErrorContent>
</ErrorBoundary>

DisplayError is a razor with nothing in the UI and the following in the code behind.

        protected override async Task OnInitializedAsync()
        {
            await DialogService.Alert("Error Occurred", "Error", new AlertOptions() { OkButtonText = "Noted"});
        }

Works perfectly for an unhandled exception in a normal page and displays an alert; very nice. And I can reset after the users closes the alert and the user continues as normal. However, if an unhandled exception was triggered from a page that is displayed in a dialog, the alert does not display. Stepping through the code, the Alert code is triggered but no alert is displayed. There probably is a reason for this or am I missing something?

You can try declaring RadzenComponents in ErrorContent as well:


<ErrorBoundary>
    <ChildContent>
        <RadzenComponents />
    </ChildContent>
    <ErrorContent>
        <RadzenComponents />
        <DisplayError />
    </ErrorContent>
</ErrorBoundary>

Thank you for your reply.

I just tried this. No luck with this.

I noted that if I put something in the UI for the DisplayError razor page, that does display but no alert regardless of whether the RadzenComponent tag is in the ErrorContent or not.

RadzenDialog/RadzenComponents needs to be part of the rendered UI otherwise DialogService will not work since the dialog will not be rendered.

I tried putting RadzenComponnets into the razor of DisplayError but that didn't work either.

Works fine from a normal page. But is this not going to work for pages from a Dialog?

Ah, I see! For the dialog you need to use ErrorBoundary like this:

async Task ShowInlineDialog()
{
    var result = await DialogService.OpenAsync("Simple Dialog", ds =>
    @<ErrorBoundary>
        <ChildContent>
            <RadzenButton Text="Throw" Click="@(args => throw new Exception("This is a test exception"))" />
        </ChildContent>
        <ErrorContent>
            <DisplayError />
        </ErrorContent>
    </ErrorBoundary>);
}

Here is the full app: https://drive.google.com/file/d/1uVZg9bsskwjaUFNFt-jRWT1O7CcdVyrm/view?usp=sharing

If you want to handle errors in all dialogs without modifing the app code that shows the dialogs you can do:
MainLayout.razor

<ErrorBoundary>
    <ChildContent>
        <RadzenComponents />
    </ChildContent>
    <ErrorContent>
        <RadzenComponents />
        <DisplayError />
    </ErrorContent>
</ErrorBoundary>

Home.razor

<RadzenButton Text="Show dialog" Click="@ShowInlineDialog" />

@code {
    async Task ShowInlineDialog()
    {
        var result = await DialogService.OpenAsync("Simple Dialog", ds =>
        @<RadzenButton Text="Throw" Click="@(args => throw new Exception("This is a test exception"))" />);
    }
}

...
DisplayError.razor

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        await DialogService.Alert("Error Occurred", "Error", new AlertOptions() { OkButtonText = "Noted" });
    }

Great, thank you, it's working. I went with the MainLayout version so that it applies to all pages displayed using a Dialog. The OnAfterRender made the difference as I was using the OnInitialize.

There are a couple of things that I need to look at:

  1. The alert dialog on the normal pages now displays 2 of the same dialogs at the same time but that from the dialog page is fine
  2. Whilst for the normal page, Recover() works to make the pages operation after the alert, it doesn't for the dialog page; not clear why this is the case.

If you have any ideas on this, I would much appreciate it. I will tomorrow have a bit more look at this as it's very very close to the solution & probably needs a slight tweak.

The double dialogs for the normal page is fixed if I only have RadzenComponents in the ErrorContent for Dialogs.

So I'm still left with Recover() not working from a dialog page eventhough the line with the Recover() code is executed

Not what's the code of this method. Can you post it?

Appropriate code from MainLayout.razor

<GlobalErrorHandler FromDialog="true">
    <RadzenComponents />
</GlobalErrorHandler>
<GlobalErrorHandler>
    @Body
</GlobalErrorHandler>

GlobalErrorHandler

<ErrorBoundary @ref="errorBoundary">
    <ChildContent>
        @ChildContent
    </ChildContent>
    <ErrorContent>
        @if (FromDialog)
        {
            <RadzenComponents />
        }
        <GlobalErrorAlert Exception="@context" OnErrorHandled="ResetErrorBoundary" />
    </ErrorContent>
</ErrorBoundary>

Codebehind

using Microsoft.AspNetCore.Components;

namespace Portal.Components.Pages
{
    public partial class GlobalErrorHandler
    {
        [Parameter]
        public RenderFragment? ChildContent { get; set; }

        [Parameter]
        public bool FromDialog { get; set; } = false;

        private ErrorBoundary? errorBoundary;

        private void ResetErrorBoundary()
        {
            errorBoundary?.Recover();
        }
    }
}

GlobalErrorAlert

using Microsoft.AspNetCore.Components;
using Portal.Shared;

namespace Portal.Components.Pages
{
    public partial class GlobalErrorAlert
    {
        [Inject]
        protected DialogService DialogService { get; set; }

        [Parameter, EditorRequired]
        public Exception Exception { get; set; }

        [Parameter, EditorRequired]
        public EventCallback OnErrorHandled { get; set; }

        //protected override async Task OnInitializedAsync()
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            await base.OnAfterRenderAsync(firstRender);

            if (firstRender == false)
            {
                return;
            }

            var errorMsg = $"There was an unhandled exception.<br /><br />{Exception.GetBaseException().Message}";

            await ShowDialogWithHtml.ShowAlert(DialogService, errorMsg, "Unhandled Error");

            await OnErrorHandled.InvokeAsync();
        }
    }
}

Not sure where this is set? Here what I've did and it worked:
MainLayout.razor

@inherits LayoutComponentBase
<ErrorBoundary @ref="dialogErrorBoundary">
    <ChildContent>
        <RadzenComponents />
    </ChildContent>
    <ErrorContent>
        <RadzenComponents />
        <DisplayError ErrorBoundary="@dialogErrorBoundary" />
    </ErrorContent>
</ErrorBoundary>
<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <ErrorBoundary @ref="bodyErrorBoundary">
                <ChildContent>
                    @Body
                </ChildContent>
                <ErrorContent>
                    <DisplayError ErrorBoundary="@bodyErrorBoundary" />
                </ErrorContent>
            </ErrorBoundary>
        </article>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="." class="reload">Reload</a>
    <span class="dismiss">🗙</span>
</div>
@code {
    ErrorBoundary? dialogErrorBoundary;
    ErrorBoundary? bodyErrorBoundary;
}

DisplayError.razor

@inject DialogService DialogService

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        await DialogService.Alert("Error Occurred", "Error", new AlertOptions() { OkButtonText = "Noted" });

        ErrorBoundary.Recover();
    }

    [Parameter, EditorRequired]
    public ErrorBoundary ErrorBoundary { get; set; }
}

Thank you.

I have simplified my code based on yours.

The body one still works fine but the dialog one has a slight issue. This is the same issue I was having with my version as well after I got the alert working based on your code.

I have a CRUD with a datagrid and a dialog that displays an Edit Page. When I trigger an exception from this edit page, the alert displays fine now, and the Recover() code executes but the Edit Page no longer displays from the datagrid afterwards. If I navigate to another page and come back to this datagrid, the edit page can now be displayed. Seems like the Recover() hasn’t quite worked.

If you are able to check this with your version, that would be great. I’m happy to create a solution & post it if you need it.

I found this on YouTube

At 5:20 onwards talks about ErrorBoundary in Router and not around @Body (at 6:30 onwards). But I couldn’t get @Bodyhis to work for me