Skip to content

Additional programmatic render mode guidance#31405

Closed
guardrex wants to merge 1 commit intomainfrom
guardrex-patch-3
Closed

Additional programmatic render mode guidance#31405
guardrex wants to merge 1 commit intomainfrom
guardrex-patch-3

Conversation

@guardrex
Copy link
Copy Markdown
Collaborator

@guardrex guardrex commented Jan 10, 2024

Addresses #31403
Addresses #28161

Follow-up to PR earlier this morning #31404 that added a new section on programmatic render modes. There are some additional scenarios to cover in the section.

Mackinnon ... two problems with what I'm adding ...

  • The description of the code is very likely incorrect 🙈 (or at least incomplete) ...

    set the render mode of the app dynamically based on the render modes of the components in the app

    What is this code doing? ... because when the router becomes interactive it seems like whatever was set on Routes is what is then going to be used thereafter for each navigation ... well 🤔 ... is it that the router will continue working interactively until a route for a static SSR component can't be matched? ... and then a request is made back to the server, where the root component re-renders and then sucks in the render mode of the requested component and applies that to the Routes component?

  • Why bother? 😄 ... I don't understand what this approach going to do for devs? I think we need to tell devs exactly what scenario(s) this approach applies to.

    This PR is based on the cross-linked discussion between a dev and Javier at Clarify Blazor Web Route behavior aspnetcore#52176. It looks like a workaround to avoid the flash-over from the server-rendered error page to the NotFound router content. Also possibly related are these various NotFound/NotAuthorized no-op situations, where under static SSR the server isn't going to render content from those components ... described by Javier at Clarify Blazor Web Route behavior aspnetcore#52176 (comment).

    If you look just above what this PR is adding, I placed some content earlier this morning on what seems like a simpler scenario where there's an app that defaults to static SSR components or per-component render modes but has this one area of components that you would like to explicitly configure globally for interactive SSR. Let me know if that bit is correct, but I want to mention that it seems simpler to understand why one would do that ... e.g., the Admin area of pages/components that the example uses. The dev wants to globally apply interactive SSR to that area of administrator pages and default the rest to static SSR or per-component render mode assignment. For the new content that I'm seeking to add on this PR, it's not as clear why one would adopt it, and I hope we can add a why to the approach.

I'll also mention that I'm working a related issue at #31402. I'm going sleep on it because I just hit a brain fry 🧠🔥. I'll test locally soon with some scenarios to experience what's being discussed in PU issues (not authorized and not found scenarios) and figure out what updates to draft.


Internal previews

📄 File 🔗 Preview link
aspnetcore/blazor/components/render-modes.md ASP.NET Core Blazor render modes

@MackinnonBuck
Copy link
Copy Markdown
Member

What is this code doing? ... because when the router becomes interactive it seems like whatever was set on Routes is what is then going to be used thereafter for each navigation ... well 🤔 ... is it that the router will continue working interactively until a route for a static SSR component can't be matched? ... and then a request is made back to the server, where the root component re-renders and then sucks in the render mode of the requested component and applies that to the Routes component?

It sounds like your understanding is pretty accurate - as soon as the router becomes interactive, it will stay that way until the page gets reloaded. That's because the logic determining the RenderModeForPage only runs during SSR. If you want to "escape" back to being server-side rendered, you'll need to do that manually.

The way we've handled this in the Blazor Web app template (with individual auth and app-level interactivity) is we have a layout component that applies to all the "Account" pages whose purpose is to do a full page reload when the page gets navigated to interactively:

@inherits LayoutComponentBase
@layout MainLayout
@inject NavigationManager NavigationManager

@if (HttpContext is null)
{
    <p>Loading...</p>
}
else
{
    @Body
}

@code {
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    protected override void OnParametersSet()
    {
        if (HttpContext is null)
        {
            // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext.
            // The identity pages need to set cookies, so they require an HttpContext. To achieve this we
            // must transition back from interactive mode to a server-rendered page.
            NavigationManager.Refresh(forceReload: true);
        }
    }
}

Customers will likely need to do something similar if they want part of their app to have top-level interactivity and the rest to using static server rendering.

Why bother?

The best real-world example I can think of is the Blazor Web app template with Identity pages and top level interactivity enabled. In that template, the developer expressed that their desired rendering strategy is to have top-level interactivity. Maybe the customer's app is highly interactive, so it doesn't make sense to add the complexity of SSR when it doesn't provide much benefit. However, the identity pages are incompatible with interactive rendering - they rely heavily on the HttpContext, utilize cookies, etc.

So now you have two parts of the app that have fundamentally different requirements and rendering strategies, and you need a way to transition from one style to another. That's where this approach would be useful.

@guardrex
Copy link
Copy Markdown
Collaborator Author

guardrex commented Jan 18, 2024

do a full page reload when the page gets navigated to interactively:

For completeness in the discussion, here's the other piece to make it static SSR on the reload ...

<Routes @rendermode="@RenderModeForPage" />

...

@code {
    ...

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

Customers will likely need to do something similar if they want part of their app to have top-level interactivity and the rest to using static server rendering.

Yes, I think this whole technique should be described. I'll work on it.

@guardrex
Copy link
Copy Markdown
Collaborator Author

guardrex commented Jan 18, 2024

@MackinnonBuck ... You know what .... this approach ...

<Routes @rendermode="@RenderModeForPage" />
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;

private IComponentRenderMode? RenderModeForPage =>
    HttpContext.GetEndpoint()?.Metadata.GetMetadata<RenderModeAttribute>()?.Mode;

... 🤔... kind'a looks really good for the per-component approach. The project template is saying that if the static SSR component is in a defined area of the app (i.e., the /Account area), then decide to set the render mode on Routes to null. The code above says to use (reflection?) for what the component set ... so ... it looks like you don't need to have a defined area (folder) for static SSR components if you're willing to do a per-component assignment and just leave your static SSR components without an @rendermode directive.

UPDATE: Yep! It composed well on the PR at #31497 ... but don't look at that today (Thursday). I'm bound to make further updates to it Friday morning. I'll ping u for review tomorrow.

@guardrex
Copy link
Copy Markdown
Collaborator Author

I'm going to reconstitute this on a local branch for further work.

@guardrex guardrex closed this Jan 18, 2024
@guardrex guardrex deleted the guardrex-patch-3 branch January 18, 2024 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants