dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.36k stars 9.99k forks source link

Error boundaries may block redirects by handling exceptions of type `NavigationException` #52777

Closed meisam-dehghan closed 8 months ago

meisam-dehghan commented 10 months ago

When you try to navigate to another page by using the NavigationManager.NavigateTo in a blazor web app introduced in .net8,the following exception is thrown : Microsoft.AspNetCore.Components.NavigationException: 'Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.' Even if you put the logic in a try/catch block,the exception would be still thrown and the page would stay on the current route.This means that if,for example,you wanted to create a login page that should take the user to the main page after a successful login form submission,the user would still stay on the login page!

Even though the issue had previously been brought up by @danroth27 at #49143 and closed as done later on,the problem still persists in the final version of .net8.I've also created a sample project that you can check out in the following repo: Blazor Web App With Custom Cookie-based Authentication

javiercn commented 10 months ago

@meisam-dehghan thanks for contacting us.

Is there any functional impact to your app? Do you actually get redirected to the error page or similar?

This is an exception that is thrown and caught internally by the framework, and as such, you only get to see it if you are debugging and have enabled breaking on caught exceptions.

If this is the case, can you please create a minimal repro project that demonstrates the issue as a public GitHub repository we can take a look at?

ghost commented 10 months ago

Hi @meisam-dehghan. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

meisam-dehghan commented 10 months ago

@javiercn

Is there any functional impact to your app? Do you actually get redirected to the error page or similar?

Yeah, The app cant redirect to the main page after a successful login. It simply stays there. The main page will be shown with logout button only after I refresh the page manually! What's more, I'm not redirected to the error page, the page just hangs in there.

This is an exception that is thrown and caught internally by the framework, and as such, you only get to see it if you are debugging and have enabled breaking on caught exceptions.

Actually, I tried running the app without debugging as well, but the problem still persists. Here's the public repo you can clone and try for yourself: Blazor Web App With Custom Cookie-based Authentication

rob-king-volpara commented 8 months ago

I'm seeing this too. I have a Blazor SSR app and using the EditForm component.

@inject NavigationManager _navigationManager
<EditForm EditContext="editContext" OnValidSubmit="SignIn" FormName="LoginForm" Enhance>
....
</EditForm>

Nothing fancy about the form, it's just two bound fields to a model using the EditContext as per the example on the MS docs.

public void SignIn()
{
    _navigationManager.NavigateTo($"api/authentication/login?tenant={Model.Customer}&email={Model.Email}&returnUrl={ReturnUrl}", true);
}

When hitting the SignIn() method, an exception is thrown and no navigation occurs.

Unhandled exception rendering component: Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.
      Microsoft.AspNetCore.Components.NavigationException: Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.
         at Microsoft.AspNetCore.Components.Endpoints.HttpNavigationManager.NavigateToCore(String uri, Boolean forceLoad)
         at Volpara.Components.Pages.Welcome.SignIn() in C:\...\Components\Pages\Welcome.razor:line 124
         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.EventCallback`1.InvokeAsync(TValue arg)
         at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Interestingly, it doesn't seem to even need to be on a form post. For instance, I have this OnInitialized() method which throws an exception if it falls into the _navigationManager case:

protected override void OnInitialized()
{
    Model ??= new();
    editContext = new(Model);
    editContext.OnValidationRequested += CheckForm;
    messageStore = new(editContext);

    if (TenantRoute.HasValue())
    {
        CustomerInfo customer = _customerInfoService.GetCustomerInfo(TenantRoute);
        if (customer != null)
        {
            Model.Customer = TenantRoute;
        }
        else
        {
            _navigationManager.NavigateTo($"/");
        }
    }
}

I'm using SDK 8.0.101

rob-king-volpara commented 8 months ago

My current workaround is to disable enhanced forms and directly embed the script:

@if (string.IsNullOrEmpty(_redirectTo))
{
    <EditForm EditContext="_editContext" OnValidSubmit="SignIn" FormName="LoginForm">
....
    </EditForm>
}
else
{
    <script>
        window.location = "@_redirectTo";
    </script>
}
Gabriel-Paulucci commented 8 months ago

I'm receiving the same error, the only way I found to solve it is with the render mode server active.

MackinnonBuck commented 8 months ago

@meisam-dehghan, when a component is rendered using static server rendering, NavigationManager.NavigateTo() will always throw an exception. This is by design. The exception is meant to be caught by the framework so it can be handled as a redirect.

In the repro you provided, there is a try/catch around NavigationManager.NavigateTo(), so the exception never bubbles up to be handled as a redirect. If you remove the try/catch, then the app works correctly.

If you want to have a try/catch around the code with the NavigateTo() call and catch all exceptions except NavigationException, you could use a filtered exception handler:

try
{
    // ...
    NavigationManager.NavigateTo(...);
    // ...
}
catch (Exception ex) when (ex is not NavigationException)
{
    // ...
}

However, if the NavigationException bubbles up but does not get correctly caught by the framework, that might be a framework bug. This seems like what might be happening in @rob-king-volpara's case.

@rob-king-volpara, would you be able to provide a minimal repro that demonstrates the problem you're experiencing, so that we can investigate the issue further?

NaserParhizkar commented 8 months ago

My problem is similar and you can see it here https://github.com/NaserParhizkar/Orginal/blob/master/Dependent/Components/ExceptionComponent.razor

I simulate a big project with these projects which I need to authenticate users by redirecting them to Dependent project that has a App.razor component with

  private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
       ? null
       : InteractiveServer;

like default blazor version 8 template for authentication type = "individual account". The important section of my code is Mainlayout.razor file which wrap @body with exception management component

            <ExceptionComponent>
                @Body
            </ExceptionComponent>

and you can see when I override OnErrorAsync(Exception exception) at my custom ExceptionComponent NavigationManager can not redirect me to root or other pages and url fiexed there without any changes.

MackinnonBuck commented 8 months ago

Thanks @NaserParhizkar - so it looks like the real problem here is that error boundaries can handle exceptions of type NavigationException, preventing them from bubbling all the way up and getting handled by the framework as a redirect.

I'll update the title of this issue to reflect this, since it seems to be the only framework bug expressed here so far.

rob-king-volpara commented 8 months ago

@MackinnonBuck I can confirm it is the ErrorBoundary causing the issue. I have a simple repo up here https://github.com/rob-king-volpara/BlazorApp52777 which is a cut-down version of what I'm building.

If you change the layout of Home.razor from WorkingLayour.razor to FaultyLayout.razor, you will trigger the error. As you can see the FaultyLayout.razor has an ErrorBoundary component which prevents the NavigateTo(...) from working.

I should add, we are developing some Blazor WASM apps and they do not have the same issue. This seems contained to server-side.

MackinnonBuck commented 8 months ago

Thanks for providing the repro and confirming that the ErrorBoundary was the issue, @rob-king-volpara!

GerardFR commented 8 months ago

I have a similar issue on a Web App with the InteractiveWebAssembly render mode and authentication (static rendering for the Account folder pages). In the standard ForgotPassword page, when I click the Reset password button, an uncaught navigation exception is thrown, and I don't get redirected to the ForgotPasswordConfirmation page. Now, if I remove the ErrorBoundary that I have addes in the MainLayout page, redirection works as expected.

BTW, it would be nice if the fact that the NavigationException is not an error in the application was documented. I spent time trying to trace that exception that I thought was thrown because of my code.

Anyway, I'm happy I found this thread as it helps me solve my problem.