Skip to content
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

Blazor server side better error UI #8195

Open
hikalkan opened this issue Mar 25, 2021 · 10 comments
Open

Blazor server side better error UI #8195

hikalkan opened this issue Mar 25, 2021 · 10 comments

Comments

@hikalkan
Copy link
Member

We'd implemented a custom logger to show errors for Blazor WASM. For server side, global exception handling is not possible yet (see dotnet/aspnetcore#30940). However, we can still show a better messagebox (modal dialog) instead of the yellow bottom bar, for blazor server side. We can provide an option to the user to refresh the page in this modal dialog (message modal can have two options: Close & Refresh Page).
However, this should be carefully implemented. Because, if we handle every error and try to show message, it won't work. We should only handle the errors for blazor application's page, not other HTTP requests and MVC page requests.

@hikalkan hikalkan added this to the backlog milestone Mar 25, 2021
@hikalkan hikalkan modified the milestones: backlog, 5.1-preview Dec 5, 2021
@hikalkan hikalkan modified the milestones: 5.1-preview, 5.2-preview Dec 31, 2021
@hikalkan hikalkan self-assigned this Dec 31, 2021
@hikalkan hikalkan assigned berkansasmaz and unassigned hikalkan Jan 30, 2022
@berkansasmaz berkansasmaz modified the milestones: 5.2-preview, 5.3-preview Mar 4, 2022
@berkansasmaz berkansasmaz added hold Taken progress, but needs to wait something. priority:high in-progress effort-sm and removed in-progress priority:normal hold Taken progress, but needs to wait something. labels Mar 10, 2022
@berkansasmaz berkansasmaz added hold Taken progress, but needs to wait something. and removed in-progress labels Mar 17, 2022
@berkansasmaz berkansasmaz added in-progress and removed hold Taken progress, but needs to wait something. labels Mar 29, 2022
berkansasmaz added a commit that referenced this issue Apr 1, 2022
@hikalkan hikalkan modified the milestones: 5.4-preview, 6.0-preview May 24, 2022
@hikalkan hikalkan modified the milestones: 6.0-preview, 7.0-preview Jul 8, 2022
@hikalkan hikalkan self-assigned this Jul 8, 2022
@hikalkan hikalkan removed the stuck label Jul 8, 2022
@hikalkan hikalkan modified the milestones: 7.0-preview, 7.1-preview Oct 12, 2022
@hikalkan hikalkan modified the milestones: 7.1-preview, 7.3-preview Dec 27, 2022
@hikalkan hikalkan assigned berkansasmaz and unassigned berkansasmaz Mar 1, 2023
@beriniwlew
Copy link
Contributor

Any updates on this one?

@berkansasmaz
Copy link
Member

There have been investigations, but the results were unsatisfactory due to some limitations. However, we will close the problem once we have found a satisfactory solution.

@beriniwlew
Copy link
Contributor

So what's the workaround so far? Catching the exception in the component's code on the Blazor side?

If so, how do I open the exception modal with the message?

@berkansasmaz
Copy link
Member

Yes, we try to ensure that there are no unhandled exceptions as mentioned in Microsoft's documentation.

By the way, you can see the work done within the scope of this issue here.

@hikalkan hikalkan modified the milestones: 7.3-preview, 7.4-preview May 7, 2023
@hikalkan hikalkan modified the milestones: 7.4-preview, 8.0-preview Jun 4, 2023
@hikalkan hikalkan modified the milestones: 8.0-preview, 8.1-preview Aug 11, 2023
@hikalkan hikalkan modified the milestones: 8.1-preview, backlog Oct 2, 2023
@BorzoNosrati
Copy link

Abp 7.3.3
Dotnet core 7

It can be done easily by creating the ComponentBase from scratch just like the original (github):

in blazor components, every events will be called from Task IHandleEvent.HandleEventAsync so if it can be override, then we can create a handler based on the Task.Status

  1. Create the MyComponentBase:
public abstract class MyComponentBase : IComponent, IHandleEvent, IHandleAfterRender
{
    ...

    Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    {
        var task = callback.InvokeAsync(arg);
        var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
            task.Status != TaskStatus.Canceled;

        // After each event, we synchronously re-render (unless !ShouldRender())
        // This just saves the developer the trouble of putting "StateHasChanged();"
        // at the end of every event callback.
        StateHasChanged();

        return shouldAwaitTask ?
            CallStateHasChangedOnAsyncCompletion(task) :
            Task.CompletedTask;
    }

    ...

}

Then task will be run in CallStateHasChangedOnAsyncCompletion method:

    private async Task CallStateHasChangedOnAsyncCompletion(Task task)
    {
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            // Ignore exceptions from task cancellations, but don't bother issuing a state change.
            if (task.IsCanceled)
            {
                return;
            }

            throw;
        }

        StateHasChanged();
    }
  1. Instead of throw call a method for handle the task.Exception named OnCallStateHasChangedOnAsyncCompletionExceptionAsync:
protected virtual Task OnCallStateHasChangedOnAsyncCompletionExceptionAsync(AggregateException? exception)
=> Task.CompletedTask;

Make sure to set the virtual keyword to allow override this method.

  1. Now CallStateHasChangedOnAsyncCompletion would be like this:
    private async Task CallStateHasChangedOnAsyncCompletion(Task task)
    {
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            // Ignore exceptions from task cancellations, but don't bother issuing a state change.
            if (task.IsCanceled)
            {
                return;
            }

            await OnCallStateHasChangedOnAsyncCompletionExceptionAsync(task.Exception);
        }

        StateHasChanged();
    }
  1. From OwningComponentBase and AbpComponentBase rewrite it to MyOwningComponentBase and MyAbpComponentBase.

  2. In MyAbpComponentBase, the OnCallStateHasChangedOnAsyncCompletionExceptionAsync method can be override like this:

    protected override Task OnCallStateHasChangedOnAsyncCompletionExceptionAsync(AggregateException? exception)
    {
        return HandleErrorAsync(exception);
    }

HandleErrorAsync is a method in MyAbpComponentBase that appear a friendly modal message.

In the RunInitAndSetParametersAsync method also can do the same for handle errors.

    private async Task RunInitAndSetParametersAsync()
    {
        try
        {
            ...
        }
        catch (Exception ex)
        {
            await OnRunInitAndSetParametersExceptionAsync(ex);
        }

    }

    public virtual Task OnRunInitAndSetParametersExceptionAsync(Exception ex) => Task.CompletedTask;

In MyAbpComponentBase:

    public override Task OnRunInitAndSetParametersExceptionAsync(Exception ex)
    {
        return HandleErrorAsync(ex);
    }

With Regards

@rushasdev
Copy link

I bumped into it in ABP 7.4.0 commercial: Unhandled exception rendering component: Forbidden Volo.Abp.Http.Client.AbpRemoteCallException

@maliming
Copy link
Member

I bumped into it in ABP 7.4.0 commercial: Unhandled exception rendering component: Forbidden Volo.Abp.Http.Client.AbpRemoteCallException

Please check the logs of the backend app, and create a new issue. Thanks.

@pouyababaie
Copy link

Abp 7.3.3 Dotnet core 7

It can be done easily by creating the ComponentBase from scratch just like the original (github):

in blazor components, every events will be called from Task IHandleEvent.HandleEventAsync so if it can be override, then we can create a handler based on the Task.Status

  1. Create the MyComponentBase:
public abstract class MyComponentBase : IComponent, IHandleEvent, IHandleAfterRender
{
    ...

    Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    {
        var task = callback.InvokeAsync(arg);
        var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
            task.Status != TaskStatus.Canceled;

        // After each event, we synchronously re-render (unless !ShouldRender())
        // This just saves the developer the trouble of putting "StateHasChanged();"
        // at the end of every event callback.
        StateHasChanged();

        return shouldAwaitTask ?
            CallStateHasChangedOnAsyncCompletion(task) :
            Task.CompletedTask;
    }

    ...

}

Then task will be run in CallStateHasChangedOnAsyncCompletion method:

    private async Task CallStateHasChangedOnAsyncCompletion(Task task)
    {
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            // Ignore exceptions from task cancellations, but don't bother issuing a state change.
            if (task.IsCanceled)
            {
                return;
            }

            throw;
        }

        StateHasChanged();
    }
  1. Instead of throw call a method for handle the task.Exception named OnCallStateHasChangedOnAsyncCompletionExceptionAsync:
protected virtual Task OnCallStateHasChangedOnAsyncCompletionExceptionAsync(AggregateException? exception)
=> Task.CompletedTask;

Make sure to set the virtual keyword to allow override this method.

  1. Now CallStateHasChangedOnAsyncCompletion would be like this:
    private async Task CallStateHasChangedOnAsyncCompletion(Task task)
    {
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            // Ignore exceptions from task cancellations, but don't bother issuing a state change.
            if (task.IsCanceled)
            {
                return;
            }

            await OnCallStateHasChangedOnAsyncCompletionExceptionAsync(task.Exception);
        }

        StateHasChanged();
    }
  1. From OwningComponentBase and AbpComponentBase rewrite it to MyOwningComponentBase and MyAbpComponentBase.
  2. In MyAbpComponentBase, the OnCallStateHasChangedOnAsyncCompletionExceptionAsync method can be override like this:
    protected override Task OnCallStateHasChangedOnAsyncCompletionExceptionAsync(AggregateException? exception)
    {
        return HandleErrorAsync(exception);
    }

HandleErrorAsync is a method in MyAbpComponentBase that appear a friendly modal message.

In the RunInitAndSetParametersAsync method also can do the same for handle errors.

    private async Task RunInitAndSetParametersAsync()
    {
        try
        {
            ...
        }
        catch (Exception ex)
        {
            await OnRunInitAndSetParametersExceptionAsync(ex);
        }

    }

    public virtual Task OnRunInitAndSetParametersExceptionAsync(Exception ex) => Task.CompletedTask;

In MyAbpComponentBase:

    public override Task OnRunInitAndSetParametersExceptionAsync(Exception ex)
    {
        return HandleErrorAsync(ex);
    }

With Regards



i noticed some information being missed, for example  , the ```StateHasChanged()``` method does not exist in the custom component base , also , the ```HandleErrorAsync()``` method  is only available if you inherits from ```AbpComponentBase```

i quit liked the solution but the implementation needs more detail.
also  , i think it's time for @Abp to think about a solution to handle errors generally.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants