abpframework / abp

Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.91k stars 3.44k forks source link

Blazor server side better error UI #8195

Open hikalkan opened 3 years ago

hikalkan commented 3 years ago

We'd implemented a custom logger to show errors for Blazor WASM. For server side, global exception handling is not possible yet (see https://github.com/dotnet/aspnetcore/issues/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.

davidjhurtado commented 2 years ago

Good day, this fix gonna be available on version 5.4?

berkansasmaz commented 2 years ago

Good day, this fix gonna be available on version 5.4?

Yes, we expect it to be available on version 6.0.

beriniwlew commented 1 year ago

Any updates on this one?

berkansasmaz commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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.

BorzoNosrati commented 1 year ago

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 commented 1 year ago

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

maliming commented 1 year ago

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 commented 8 months ago

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.
rcalv002 commented 5 months ago

Any progress on this one?