Skip to content

Blazor Hybrid (WebViewRenderer) should support per-page/component render modes as no-ops #65875

@mattleibow

Description

@mattleibow

Tracking issue: dotnet/maui#31945

Background

The maui-blazor-web solution template enables sharing Razor components between Blazor Web and Blazor Hybrid (MAUI) apps via a shared Razor Class Library (RCL). When using per-page/component interactivity (e.g., @rendermode InteractiveAuto on a page), the Blazor Web app works fine, but the MAUI Blazor Hybrid app throws:

NotSupportedException: Cannot supply a component of type 'Counter' because the current platform does not support the render mode 'InteractiveAuto'.

This is because WebViewRenderer does not override ResolveComponentForRenderMode, and the Renderer base class throws by default.

Problem

  • WebViewRenderer (source) inherits from WebRenderer but does not override ResolveComponentForRenderMode.
  • Renderer.ResolveComponentForRenderMode (source) throws NotSupportedException by default.
  • PageContext (source) creates WebViewRenderer privately — consumers (like MAUI's BlazorWebView) have no way to provide a custom renderer or override this behavior.

Why this cannot be solved in MAUI

Both WebViewRenderer and PageContext are internal sealed in this repo. WebViewManager (which MAUI subclasses) creates PageContext in its internal AttachToPageAsync method, and PageContext creates WebViewRenderer in its constructor. There is no factory pattern, DI hook, or extensibility point that would allow MAUI to customize the renderer.

Proposed Solutions

As @SteveSandersonMS suggested in dotnet/maui#31945:

Option A (minimal — unconditional override): Override ResolveComponentForRenderMode in WebViewRenderer to treat all interactive render modes as no-ops, since Blazor Hybrid is always interactive. This follows the same pattern as RemoteRenderer and WebAssemblyRenderer:

// In WebViewRenderer.cs
protected internal override IComponent ResolveComponentForRenderMode(
    Type componentType,
    int? parentComponentId,
    IComponentActivator componentActivator,
    IComponentRenderMode renderMode)
{
    // Blazor Hybrid is always interactive — all render modes are no-ops
    return componentActivator.CreateInstance(componentType);
}

Option B (extensible): Add extensibility to PageContext or WebViewRenderer so that BlazorWebView consumers can configure render mode resolution behavior.

Option C (longer-term, from Steve's suggestion): Introduce a generic Interactive render mode that web apps resolve based on global configuration, while WebView treats as a no-op.

Option D (AppContext switch — allows host app to opt in): Add an AppContext switch that the Renderer base class checks before throwing in its default ResolveComponentForRenderMode. This follows the existing pattern used in the Components area (e.g., Microsoft.AspNetCore.Components.Unsupported.DisablePropertyInjection in ComponentFactory.cs and Microsoft.AspNetCore.Components.Routing.RegexConstraintSupport in RegexConstraintSupport.cs):

// In Renderer.cs — change the default fallback behavior
private static readonly bool _suppressRenderModeExceptions =
    AppContext.TryGetSwitch(
        "Microsoft.AspNetCore.Components.SuppressUnsupportedRenderModes",
        out var suppress) && suppress;

protected internal virtual IComponent ResolveComponentForRenderMode(
    Type componentType,
    int? parentComponentId,
    IComponentActivator componentActivator,
    IComponentRenderMode renderMode)
{
    if (_suppressRenderModeExceptions)
    {
        // When the switch is set, treat unsupported render modes as no-ops.
        // This enables Blazor Hybrid and other hosts where everything is already interactive.
        return componentActivator.CreateInstance(componentType);
    }

    throw new NotSupportedException(
        $"Cannot supply a component of type '{ componentType }' because the current platform "
        + $"does not support the render mode '{renderMode}'.");
}

The host app (e.g., MAUI) would set the switch at startup:

// In MauiProgram.cs (or set automatically by MAUI's BlazorWebView handler):
AppContext.SetSwitch(
    "Microsoft.AspNetCore.Components.SuppressUnsupportedRenderModes", true);

Or via MSBuild (no code needed):

<ItemGroup>
  <RuntimeHostConfigurationOption
    Include="Microsoft.AspNetCore.Components.SuppressUnsupportedRenderModes"
    Value="true" />
</ItemGroup>

Advantages of the AppContext switch approach:

  • Follows established patterns already in Microsoft.AspNetCore.Components
  • Host app controls the opt-in (MAUI could set this automatically in its BlazorWebView initialization)
  • Can be set via code or via <RuntimeHostConfigurationOption> in csproj
  • Works for any renderer, not just WebView
  • Minimal aspnetcore change (~5 lines in one file)

Current Workaround

Users must create a wrapper class in the shared RCL that nulls out render modes for Hybrid:

public static class InteractiveRenderSettings
{
    public static IComponentRenderMode? InteractiveServer { get; set; } = RenderMode.InteractiveServer;
    public static IComponentRenderMode? InteractiveAuto { get; set; } = RenderMode.InteractiveAuto;
    public static IComponentRenderMode? InteractiveWebAssembly { get; set; } = RenderMode.InteractiveWebAssembly;

    public static void ConfigureBlazorHybridRenderModes()
    {
        InteractiveServer = null;
        InteractiveAuto = null;
        InteractiveWebAssembly = null;
    }
}

Components must use @rendermode InteractiveRenderSettings.InteractiveAuto instead of @rendermode InteractiveAuto, and the MAUI app calls ConfigureBlazorHybridRenderModes() at startup. (Sample)

Limitations of the workaround:

  • Requires modifying every component that uses render modes
  • Does not work for 3rd-party NuGet packages that set render modes directly
  • Adds boilerplate that the framework should handle natively

Steps to Reproduce

  1. Create a project using dotnet new maui-blazor-web with InteractiveAuto render mode
  2. In the .Web project's App.razor, remove @rendermode="InteractiveAuto" from HeadOutlet and Routes
  3. In the .Shared project's Counter.razor, add @rendermode InteractiveAuto at the top
  4. Run the MAUI app → throws NotSupportedException

Impact

Related Issues

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions