Skip to content

Add an API to tell you the current render mode #49401

Description

There are multiple rendering platforms/environments:

  • WebAssembly
  • Server
  • SSR/prerendering
  • WebView
  • Anything else (bUnit, custom renderers, etc.)

We've resisted having an API that literally just tells you which one is currently in effect, reasoning that components should be agnostic to this and only vary their behavior indirectly, e.g.:

  • Is an HTTP context available? Find out by calling serviceProvider.GetService<IHttpContextAccessor>()
  • Is a JavaScript runtime reachable? Only do JS interop inside OnAfterRenderAsync, and then it won't execute if not.

What's changing?

With .NET 8, people will be writing a lot more code that's specialized to SSR. The actual UX of a component may frequently vary based on this. For example, you might choose to render some buttons in a greyed-out way for SSR/prerendering, and then light up if the component becomes interactive. Or you might render completely different UI given the restrictions of noninteractivity (reducing something to a basic HTML form that can be posted). There isn't currently a sensible way to vary UX based on whether interactivity is available; you just have to somehow know.

In the framework we also have reasons to distinguish. For example, InputBase wants to emit field names in SSR cases and not otherwise. Currently we distinguish based on the presence of a FormMappingContext, but that's a weak approximation and sometimes incorrect, since a FormMappingContext could also be present in interactive components if the developer has put one there to make a form also work in SSR mode.

Proposal

I'd recommend something modelled on how .NET reports the current OS platform as a string (making it extensible) and has helpful extension methods to identify specific ones in a strongly-typed way. The main difference though is we can't have a static API for this since it may vary within a single process. So:

  • There would be a type like class M.A.Components.ComponentPlatform(string PlatformName, bool Interactive)
  • Renderer would have ComponentPlatform Platform { get; } configured during the hosting environment startup
  • RenderHandle would have ComponentPlatform Platform => _renderer.Platform
  • ComponentBase would have ComponentPlatform Platform => _renderHandle.Platform
  • There would be extension methods like Platform.IsStaticServer(), Platform.IsInteractiveServer(), Platform.IsWebAssembly(), Platform.IsWebView(), Platform.IsInteractive(), ....
    • These could be defined within libraries that know about specific platform name strings (e.g., the WebView one can go in M.A.C.WebView, likewise for any custom hosting platforms).

I specifically do not propose we solve this with some kind of cascading parameter. We have to be careful about overusing them because they impose a cost of O(component depth) to every [CascadingParameter] declaration. In particular, adding these to InputBase is not good given how apps may have UI with many hundreds of input fields at once (e.g., in an editable grid). We should try to reserve use of [CascadingParameter] for application code and only use it in the framework in situations where we think only a small number of instances of the consumer will make sense to be used.

Likewise, I don't propose we solve this with a DI service. That again comes at a per-consumption cost. It's likely somewhat cheaper than CascadingParameter (should be pretty much a single dictionary lookup) but still is a lot more machinery than we need for this.

Update: Also offer a way to find the rendermode

Besides knowing the current environment, it may be desirable to know the rendermode. That way even if you're currently prerendering (e.g., Platform.IsStaticServer()), you can find out whether the component is scheduled to become interactive later, because it has a nonnull rendermode or is inheriting one from further up the hierarchy. You might want to change the actual shape of the render output based on whether you expect interactive buttons to light up in a moment.

Alternatives

Haven't really thought through the pros/cons, but instead of the platform being defined through a string name with extension methods to identify it, it could potentially be a type hierarchy. That would allow subclasses to provide extra custom information, while still adding extension methods on the root type that would try downcasting to the subclass they know about.

Not sure whether this is desirable or would even practically be useful. We don't consider "custom hosting models" to be a common use case, and the hosting model would be the thing constructing the instance, so it wouldn't really be very extensible anyway.

I was just trying to imagine scenarios like if M.A.C.WebView wants to be able to have platform.HasWebViewFlag(FlagName ...) which it could do by subclassing ComponentPlatform and then defining its own extension methods that read info from the subclass. Another possible approach would be putting a loosely-typed property bag on ComponentPlatform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Pillar: Complete Blazor WebPriority:0Work that we can't release withoutapi-approvedAPI was approved in API review, it can be implementedarea-blazorIncludes: Blazor, Razor ComponentsenhancementThis issue represents an ask for new feature or an enhancement to an existing onefeature-full-stack-web-uiFull stack web UI with Blazor

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions