Skip to content

Support Razor Component endpoints for UseExceptionHandler #50287

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

Closed
SteveSandersonMS opened this issue Aug 23, 2023 · 4 comments
Closed

Support Razor Component endpoints for UseExceptionHandler #50287

SteveSandersonMS opened this issue Aug 23, 2023 · 4 comments
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug.

Comments

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Aug 23, 2023

Currently it doesn't work to do this:

app.UseExceptionHandler("/Error");

... where /Error corresponds to a Razor Component like:

@page "/Error"
<h1>This is the error page</h1>

In practice what happens is that:

  • Assume some other Razor Component endpoint (SSR) throws an unhandled exception during rendering
  • ExceptionHandlerMiddlewareImpl catches the original unhandled exception, changes the endpoint to /Error (a Razor Component endpoint), and re-runs the pipeline
  • We get back into EndpointHtmlRenderer.InitializeStandardComponentServicesAsync which tries to initialize the same scoped services for a second time.
  • They don't support being initialized for a second time. The first thing that happens to throw is NavigationManager.Initialize (as reported at HttpNavigationManager already initialized on Blazor SSR. #49456)
  • The system falls back on showing the standard error page

Difficulties

This isn't straightforward to resolve for two main reasons:

  1. It really isn't valid to reinitialize or even reuse the same scoped services when re-running the pipeline. The concept of re-running the pipeline without creating a new DI scope is extremely dubious since by definition, the old DI scope is now in an undefined error state - for security, Blazor Server is extremely careful not to let things keep running once they get into an undefined state.
    • Possible solution: We could change RazorComponentEndpointInvoker to guarantee we create a new DI scope for the error endpoint. This would be inconsistent with MVC etc but at least safer. And would solve the double-initialization exceptions.
  2. Even if we can make the endpoint render, it's going to behave badly if you have an interactive router in your layout. Consider:
    • Some Razor Component endpoint at /MyComponentThatThrows throws an unhandled exception
    • The error middleware runs Error.razor, which uses the same Layout.razor, which contains a @rendermode=Server router and <script src="blazor.web.js"></script>
    • The error page starts up a new circuit and runs the router on the server.
    • It will still see the current URL /MyComponentThatThrows and hence now re-runs the same component that throws a second time
    • Possible solution: We somehow would have to emit info into the error page markup to tell the JS-side code not to start any interactive runtime. We might get away with just putting that into the default project template but it would be nasty and error prone if people try to create their own error page from scratch.
@SteveSandersonMS SteveSandersonMS added the area-blazor Includes: Blazor, Razor Components label Aug 23, 2023
@javiercn
Copy link
Member

javiercn commented Aug 23, 2023

@SteveSandersonMS we can put some metadata in the HttpContext to skip initializing the services a second time. Dump all root components and "start fresh" to render the error page in the existing context.

sequenceDiagram
    participant Browser
    participant Renderer
    participant ErrorHandler
    Browser->>Renderer: Render Page
    Renderer->>Renderer: Initialize services
    Browser->>Renderer: Render page
    Renderer->>Renderer: Error
    Renderer->>ErrorHandler: Catch error
    ErrorHandler->>Renderer: Render error page
    alt HandlingError
        Renderer->>Renderer: Avoid reinitializing services
    end
    Renderer->>Browser: HTML Error
Loading

This does not have to be specific to the error handler, we can put a marker on the renderer and have a way to "reset it" (dispose all root components and start over)

@SteveSandersonMS
Copy link
Member Author

That's how I started implementing a solution yesterday before realising the broader problems listed above.

@marinasundstrom
Copy link

marinasundstrom commented Aug 23, 2023

I'm able to catch common errors like 500 (Internal Server Error) and 402 (Not found) in SSR and redirect to an error page using this:

app.UseStatusCodePagesWithRedirects("/error/{0}");

But somehow it doesn't work for 401 (Unauthorized). It is not caught by the handler.

@mkArtakMSFT mkArtakMSFT added the bug This issue describes a behavior which is not expected - a bug. label Aug 23, 2023
@mkArtakMSFT mkArtakMSFT added this to the .NET 9 Planning milestone Aug 23, 2023
@javiercn
Copy link
Member

javiercn commented Oct 24, 2023

@SteveSandersonMS I did this? Reopen if this is tracking something else.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

No branches or pull requests

4 participants