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
相关产品推荐
相关产品推荐

