You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor实现全WASM SPA同时支持服务器页面渲染的最优方案

基于WASM的SPA混合服务器端渲染实现方案

目标与约束

打造基于WASM的单页应用(SPA),路由等核心功能均基于WASM实现,同时支持服务器端页面渲染,以获取最大灵活性。

渲染规则(按页面归属区分)

  • 仅客户端页面:采用无预渲染的InteractiveWebAssemblyRenderMode渲染
  • 仅服务器页面:采用带预渲染的InteractiveServerRenderMode渲染
  • 同时存在于客户端和服务器的页面:采用带预渲染的InteractiveWebAssemblyRenderMode渲染

服务器端与客户端均存在的页面,会自动在客户端添加对应路由链接。

服务器端组件实现代码

Program.cs

...
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode();

app.Use404FallbackToClient();

app.Run();

ApplicationBuilderExtensions.cs

public static void Use404FallbackToClient(this IApplicationBuilder builder)
{
    builder.Use(async (context, next) =>
    {
        await next();

        // 检查响应状态码是否为404且请求路径不以"/_framework"开头
        if (context.Response.StatusCode == StatusCodes.Status404NotFound && !context.Request.Path.StartsWithSegments("/_framework"))
        {
            // 为Razor组件渲染创建新端点
            var endpointFeature = context.Features.Get<IEndpointFeature>();
            if (endpointFeature != null)
            {
                endpointFeature.Endpoint = new RouteEndpoint(
                    async httpContext =>
                    {
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        // 渲染Blazor组件(此处为App或自定义组件)
                        var result = new RazorComponentResult<App>();
                        await result.ExecuteAsync(httpContext);
                    },
                    RoutePatternFactory.Parse("/404"),
                    0,
                    new EndpointMetadataCollection(new[] { new ComponentTypeMetadata(typeof(_404)) }),
                    "404 Endpoint"
                );
            }

            // 直接渲染Razor组件
            await endpointFeature.Endpoint.RequestDelegate(context);
        }
    });
}

App.razor

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="css/bootstrap.min.css" />
    <link rel="stylesheet" href="css/app.css" />
    <link rel="stylesheet" href="Sindikat.Web.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet @rendermode="RenderModeForPage" />
    <RadzenTheme Theme="material" @rendermode="RenderModeForPage" />
</head>

<body>
    @if (ServerOnlyPage)
    {
        <Routes @rendermode="RenderModeForPage" />
    }
    else
    {
        <Sindikat.Web.Client.Components.Routes @rendermode="RenderModeForPage" />
    }
    <script src="_framework/blazor.web.js"></script>
    <script src="_content/Radzen.Blazor/Radzen.Blazor.js?v=@(typeof(Radzen.Colors).Assembly.GetName().Version)"></script>
    <script src="js/sindikatAppBundle.js"></script>
</body>

</html>

对应的C#逻辑代码:

public partial class App
{
    private static Assembly serverAssembly;
    private static Assembly clientAssembly;

    static App()
    {
        serverAssembly = typeof(Program).Assembly;
        clientAssembly = typeof(Sindikat.Web.Client.ModuleDefinitions.Module).Assembly;
    }

    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? RenderModeForPage { get; set; }
    private bool ServerOnlyPage;

    protected override void OnInitialized()
    {
        var endpointFeature = HttpContext.Features.Get<IEndpointFeature>();
        var type = endpointFeature.Endpoint?.Metadata.GetMetadata<ComponentTypeMetadata>()?.Type;

        var hasServerPage = type?.Assembly == serverAssembly;
        var hasClientPage = clientAssembly.GetType($"Sindikat.Web.Client.Components.Pages.{type.Name}") != null;

        ServerOnlyPage = hasServerPage && !hasClientPage;

        var renderMode = type?.GetRenderMode();

        if (renderMode != null)
            RenderModeForPage = renderMode;
        else
            RenderModeForPage = ServerOnlyPage ?
                new InteractiveServerRenderMode() :
                new InteractiveWebAssemblyRenderMode(hasServerPage);
    }
}

Routes.razor

@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@using Sindikat.Web.Client.Components

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteFound RouteData="routeData" />
    </Found>
</Router>

客户端组件实现代码

Home.razor

所有页面仅承载客户端内容组件:

@page "/"

<Sindikat.Web.Client.Components.Home></Sindikat.Web.Client.Components.Home>

Routes.razor

@using Microsoft.AspNetCore.Components.WebAssembly.Hosting

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteFound RouteData="routeData" />
    </Found>
</Router>

404.razor

@page "/404"

内容的提问来源于stack exchange,提问作者Hrvoje Batrnek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:02:35