Description
Background and Motivation
The original feature goal was “Allow static SSR pages within a globally-interactive site” but after a lot of circling around designs, we distilled this down to just one very tiny framework feature that isn’t actually anything to do with rendermodes at all. It turns out people can already do everything they would want in user code, except for one thing: once the site is running with an interactive router, escape from the interactive routing when navigating to specific pages.
So conceptually, the new feature is: have a way for component endpoints (i.e., Blazor “@page” components in a Blazor Web app) to opt out from being seen by interactive routing. The interactive router acts as if those pages don’t exist, so if you do follow a link to them, it will behave as an external navigation and does a full-page reload.
PR: #55157
Proposed API
// Microsoft.AspNetCore.Components.dll
namespace Microsoft.AspNetCore.Components;
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class AllowInteractiveRoutingAttribute : Attribute
+ {
+ public AllowInteractiveRoutingAttribute(bool allow)
+ public bool Allow { get; }
+ }
Behavior: the built-in component sees this and excludes any such pages from its route table, so any navigations to those URLs aren’t resolved via client-side navigation and instead fall back on a full page load.
// Microsoft.AspNetCore.Components.Endpoints.dll
namespace Microsoft.AspNetCore.Components.Routing;
+ public static class RazorComponentsEndpointHttpContextExtensions
+ {
+ /// <summary>
+ /// Determines whether the current endpoint is a Razor component that can be reached through
+ /// interactive routing. This is true for all page components except if they declare the
+ /// attribute <see cref="AllowInteractiveRoutingAttribute"/> with value <see langword="false"/>.
+ /// </summary>
+ /// <param name="context">The <see cref="HttpContext"/>.</param>
+ /// <returns>True if the current endpoint is a Razor component that does not declare <see cref="AllowInteractiveRoutingAttribute"/> with value <see langword="false"/>.</returns>
+ public static bool AllowsInteractiveRouting(this HttpContext? context)
+ }
Usage Examples
This is just a shorthand for convenience. Technically developers could already read endpoint metadata and see whether AllowInteractiveRoutingAttribute is there, but it’s several lines of messy code with lots of null-coalescing, and this reduces it to something much simpler and more convenient.
Normal usage in a statically rendered routable component:
@page "/static"
@attribute [AllowInteractiveRouting(false)]
<h1>Static page</h1>
Normal usage example in App.razor:
@code {
[CascadingParameter] public HttpContext? HttpContext { get; set; }
IComponentRenderMode? PageRenderMode => HttpContext.AllowsInteractiveRouting() ? RenderMode.InteractiveServer : null;
}
However that’s only one of many possibilities. Developers could write arbitrary code to select rendermodes any way they like – it could vary by the page URL or by the current browser type or anything else they like.
Alternative Designs
We could implement an end-to-end “static SSR pages in global interactivity” feature so that no App.razor logic is are required. However after a lot of design discussions we decided that’s a bad idea because it ends up being tied to particular architectural patterns instead of being a general, low-level, composable feature.
Or we could introduce a completely new “StaticSSR” rendermode, but that is massively more involved and opens up many other scenarios where people can get confused and think things will behave differently than they will (e.g., trying to use StaticSSR rendermode on things other than page components). And even if we did that it wouldn’t avoid the need to have special logic for it in App.razor equivalents, otherwise the root would already set an interactive rendermode that can’t be changed by the page.
The team has already been through quite a few iterations here so we’re reasonably confident on the conceptual approach.
Risks
It still leaves developers to copy/paste something from docs like the “PageRenderMode” expression above, as they are unlikely to figure out an end-to-end solution on their own here. However, we still prefer this because it avoids tying us to specific architectural patterns or introducing much broader new concepts.