Skip to content
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

API Proposal: AllowInteractiveRoutingAttribute for Blazor #55204

Closed
halter73 opened this issue Apr 18, 2024 · 2 comments
Closed

API Proposal: AllowInteractiveRoutingAttribute for Blazor #55204

halter73 opened this issue Apr 18, 2024 · 2 comments
Labels
api-approved API was approved in API review, it can be implemented area-blazor Includes: Blazor, Razor Components
Milestone

Comments

@halter73
Copy link
Member

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.

@halter73 halter73 added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Apr 18, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Apr 18, 2024
@halter73 halter73 added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Apr 18, 2024
Copy link
Contributor

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@halter73
Copy link
Member Author

API review notes:

  • Is this primarily intended to disable interactivity? If we look at other attributes with “Allow*”, “AllowNull” and “AllowAnonymous”, we can see they don’t have any parameter on them. They also have an opposite attribute of “DisallowNull” and “Authorize”. Another pattern we have is Enable/Disable, e.g. “EnableCors”/”DisableCors”.
    • The attribute does not disable or enable interactivity. It only controls whether the page participates in interactive routing, which is a Blazor-specific concept. If false, this means that the interactive router will not route to it, and hence inbound navigations will fall back on a full page load instead of resolving the navigation client-side.
    • Settings its value to “true” is only useful when subclassing – you can do that to override the inherited value. The default is true, so there’s no need to set it to that value unless you’re overriding.
    • We could have a pair of attributes, [EnableInteractiveRouting] and [DisableInteractiveRouting] but TBH it seems a lot clearer to have a single attribute since then the override semantics are defined by .NET’s attribute overriding semantic rather than being something we have to document and explain. Also we have other cases in Blazor of attributes with a bool flag, e.g., [StreamRendering(true)], so it follows the theme.
  • It might be misleading to even have the word “interactive” in this name. Another even more accurate and specific name would be [AllowClientRouting] (or [DisableClientRouting] if we do want to phrase it in the negative).
    • It’s client-side (SPA-style) routing that happens whether you’re on Blazor WebAssembly or Blazor Server. However I see this introduces yet another possible confusion as some people may think “client” refers only to WebAssembly. So I guess we are better sticking with “Interactive”
  • What about [ExcludeFromInteractiveRouting] so the more common usage doesn't require passing a parameter?
    • Sounds good.
  • Should we keep a bool “exclude” parameter/property to mean essentially the opposite of “allow” in the original proposal?
    • [ExcludeFromInteractiveRouting(false)] is a weird double negative that would hardly ever be used.
    • Considering the only use of [ExcludeFromInteractiveRouting(false)] would be to override an attribute in the parent class, we should simply remove the bool for now. It would be easy to add later if there’s a need for it.
  • We should also seal the attribute.
  • Now that "allow" is no longer part of the attribute name, do we still like AllowsInteractiveRouting() for the extension method?
    • Let's go with AcceptsInteractiveRouting() instead.
  • Does the this HttpContext? context parameter need to be nullable?
    • The HttpContext should never be null in App.razor where we expect this to be used. Our project template would throw a NullReferenceException if that were the case because it just does the following today, but I have not heard any complaints.
    • It would be better for AcceptsInteractiveRouting() to throw an ArgumentNullException in this case rather than silently return false since something must have gone wrong.
  • Are we happy the namespaces, name of the extension method class, the AttributeUsage?
    • While we often put extension methods in the namespace of the thing they're extending, we don't want every consumer of HttpContext to see AcceptsInteractiveRouting(), so Microsoft.AspNetCore.Components.Routing seems fine.
    • Everything else looks good.

API Approved!

// Microsoft.AspNetCore.Components.dll
namespace Microsoft.AspNetCore.Components;

+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public sealed class ExcludeFromInteractiveRoutingAttribute : Attribute
+ {
+ }

// Microsoft.AspNetCore.Components.Endpoints.dll
namespace Microsoft.AspNetCore.Components.Routing;

+ public static class RazorComponentsEndpointHttpContextExtensions
+ {
+     public static bool AcceptsInteractiveRouting(this HttpContext context)
+ }

@halter73 halter73 added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Apr 18, 2024
@javiercn javiercn added this to the 9.0-preview4 milestone Apr 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

2 participants