dotnet-blazor-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-blazor-patterns
.NET Blazor 设计模式
Blazor hosting models, render modes, project setup, routing, enhanced navigation, streaming rendering, and AOT-safe patterns. Covers all five hosting models (InteractiveServer, InteractiveWebAssembly, InteractiveAuto, Static SSR, Hybrid) with trade-off analysis for each.
Blazor托管模型、渲染模式、项目搭建、路由、增强型导航、流式渲染以及AOT安全设计模式。涵盖全部五种托管模型(InteractiveServer、InteractiveWebAssembly、InteractiveAuto、Static SSR、Hybrid),并对每种模型的取舍进行分析。
Scope
适用范围
- Blazor Web App project setup and configuration
- Hosting model selection (Server, WASM, Auto, SSR, Hybrid)
- Render mode configuration (global, per-page, per-component)
- Routing and enhanced navigation
- Streaming rendering and prerendering
- AOT-safe Blazor patterns
- Blazor Web App项目搭建与配置
- 托管模型选择(Server、WASM、Auto、SSR、Hybrid)
- 渲染模式配置(全局、按页面、按组件)
- 路由与增强型导航
- 流式渲染与预渲染
- AOT安全的Blazor设计模式
Out of scope
不适用范围
- Component architecture (lifecycle, state, JS interop) -- see [skill:dotnet-blazor-components]
- Authentication across hosting models -- see [skill:dotnet-blazor-auth]
- bUnit component testing -- see [skill:dotnet-blazor-testing]
- Standalone SignalR patterns -- see [skill:dotnet-realtime-communication]
- Browser-based E2E testing -- see [skill:dotnet-playwright]
- UI framework selection decision tree -- see [skill:dotnet-ui-chooser]
Cross-references: [skill:dotnet-blazor-components] for component architecture, [skill:dotnet-blazor-auth] for authentication, [skill:dotnet-blazor-testing] for bUnit testing, [skill:dotnet-realtime-communication] for standalone SignalR, [skill:dotnet-playwright] for E2E testing, [skill:dotnet-ui-chooser] for framework selection, [skill:dotnet-accessibility] for accessibility patterns (ARIA, keyboard nav, screen readers).
- 组件架构(生命周期、状态、JS互操作)——详见[skill:dotnet-blazor-components]
- 跨托管模型的身份验证——详见[skill:dotnet-blazor-auth]
- bUnit组件测试——详见[skill:dotnet-blazor-testing]
- 独立SignalR设计模式——详见[skill:dotnet-realtime-communication]
- 基于浏览器的端到端测试——详见[skill:dotnet-playwright]
- UI框架选择决策树——详见[skill:dotnet-ui-chooser]
交叉引用:组件架构请参考[skill:dotnet-blazor-components],身份验证请参考[skill:dotnet-blazor-auth],bUnit测试请参考[skill:dotnet-blazor-testing],独立SignalR请参考[skill:dotnet-realtime-communication],端到端测试请参考[skill:dotnet-playwright],框架选择请参考[skill:dotnet-ui-chooser],无障碍设计模式(ARIA、键盘导航、屏幕阅读器)请参考[skill:dotnet-accessibility]。
Hosting Models & Render Modes
托管模型与渲染模式
Blazor Web App (.NET 8+) is the default project template, replacing the separate Blazor Server and Blazor WebAssembly templates. Render modes can be set globally, per-page, or per-component.
Blazor Web App(.NET 8+)是默认项目模板,替代了之前独立的Blazor Server和Blazor WebAssembly模板。渲染模式可全局设置,也可按页面或组件单独设置。
Render Mode Overview
渲染模式概述
| Render Mode | Attribute | Interactivity | Connection | Best For |
|---|---|---|---|---|
| Static SSR | (none / default) | None -- server renders HTML, no interactivity | HTTP request only | Content pages, SEO, forms with minimal interactivity |
| InteractiveServer | | Full | SignalR circuit | Low-latency interactivity, full server access, small user base |
| InteractiveWebAssembly | | Full (after download) | None (runs in browser) | Offline-capable, large user base, reduced server load |
| InteractiveAuto | | Full | SignalR initially, then WASM | Best of both -- immediate interactivity, eventual client-side |
| Blazor Hybrid | | Full (native) | None (runs in-process) | Desktop/mobile apps with web UI, native API access |
| 渲染模式 | 属性 | 交互性 | 连接方式 | 最佳适用场景 |
|---|---|---|---|---|
| Static SSR | (无/默认) | 无——服务器渲染HTML,无交互能力 | 仅HTTP请求 | 内容页面、SEO、交互需求极少的表单 |
| InteractiveServer | | 完全交互 | SignalR回路 | 低延迟交互、需完整服务器API访问、用户规模较小 |
| InteractiveWebAssembly | | 完全交互(下载完成后) | 无(在浏览器中运行) | 支持离线使用、用户规模大、降低服务器负载 |
| InteractiveAuto | | 完全交互 | 初始使用SignalR,之后切换为WASM | 兼顾两者优势——即时交互,最终切换至客户端运行 |
| Blazor Hybrid | MAUI/WPF/WinForms中的 | 完全交互(原生环境) | 无(进程内运行) | 采用Web UI的桌面/移动应用、需访问原生API |
Per-Mode Trade-offs
各模型的取舍对比
| Concern | Static SSR | InteractiveServer | InteractiveWebAssembly | InteractiveAuto | Hybrid |
|---|---|---|---|---|---|
| First load | Fast | Fast | Slow (WASM download) | Fast (Server first) | Instant (local) |
| Server resources | Minimal | Per-user circuit | None after download | Circuit then none | None |
| Offline support | No | No | Yes | Partial | Yes |
| Full .NET API access | Yes (server) | Yes (server) | Limited (browser sandbox) | Varies by phase | Yes (native) |
| Scalability | High | Limited by circuits | High | High (after WASM) | N/A (local) |
| SEO | Yes | Prerender | Prerender | Prerender | N/A |
| 考量维度 | Static SSR | InteractiveServer | InteractiveWebAssembly | InteractiveAuto | Hybrid |
|---|---|---|---|---|---|
| 首次加载速度 | 快 | 快 | 慢(需下载WASM) | 快(初始为Server模式) | 即时(本地运行) |
| 服务器资源消耗 | 极少 | 每个用户对应一个回路 | 下载完成后无消耗 | 初始回路消耗,之后无 | 无 |
| 离线支持 | 否 | 否 | 是 | 部分支持 | 是 |
| 完整.NET API访问 | 是(服务器端) | 是(服务器端) | 受限(浏览器沙箱) | 随阶段变化 | 是(原生环境) |
| 可扩展性 | 高 | 受回路数量限制 | 高 | 高(切换至WASM后) | 不适用(本地运行) |
| SEO支持 | 是 | 支持预渲染 | 支持预渲染 | 支持预渲染 | 不适用 |
Setting Render Modes
设置渲染模式
Global (App.razor):
razor
<!-- Sets default render mode for all pages -->
<Routes @rendermode="InteractiveServer" />Per-page:
razor
@page "/dashboard"
@rendermode InteractiveServer
<h1>Dashboard</h1>Per-component:
razor
<Counter @rendermode="InteractiveWebAssembly" />Gotcha: Without an explicit render mode boundary, a child component cannot request a more interactive render mode than its parent. However, interactive islands are supported: you can place an attribute on a component embedded in a Static SSR page to create a render mode boundary, enabling interactive children under otherwise static content.
@rendermode全局设置(App.razor):
razor
<!-- 为所有页面设置默认渲染模式 -->
<Routes @rendermode="InteractiveServer" />按页面设置:
razor
@page "/dashboard"
@rendermode InteractiveServer
<h1>Dashboard</h1>按组件设置:
razor
<Counter @rendermode="InteractiveWebAssembly" />注意事项: 如果没有明确的渲染模式边界,子组件无法请求比父组件更高交互性的渲染模式。不过支持交互式孤岛:你可以在Static SSR页面中嵌入的组件上添加属性来创建渲染模式边界,从而在静态内容下启用交互式子组件。
@rendermodeProject Setup
项目搭建
Blazor Web App (Default Template)
Blazor Web App(默认模板)
bash
undefinedbash
undefinedCreates a Blazor Web App with InteractiveServer render mode
创建采用InteractiveServer渲染模式的Blazor Web App
dotnet new blazor -n MyApp
dotnet new blazor -n MyApp
With specific interactivity options
指定交互模式选项
dotnet new blazor -n MyApp --interactivity Auto # InteractiveAuto
dotnet new blazor -n MyApp --interactivity WebAssembly # InteractiveWebAssembly
dotnet new blazor -n MyApp --interactivity Server # InteractiveServer (default)
dotnet new blazor -n MyApp --interactivity None # Static SSR only
undefineddotnet new blazor -n MyApp --interactivity Auto # InteractiveAuto模式
dotnet new blazor -n MyApp --interactivity WebAssembly # InteractiveWebAssembly模式
dotnet new blazor -n MyApp --interactivity Server # InteractiveServer模式(默认)
dotnet new blazor -n MyApp --interactivity None # 仅Static SSR模式
undefinedBlazor Web App Project Structure
Blazor Web App项目结构
MyApp/
MyApp/ # Server project
Program.cs # Host builder, services, middleware
Components/
App.razor # Root component (sets global render mode)
Routes.razor # Router component
Layout/
MainLayout.razor # Main layout
Pages/
Home.razor # Static SSR by default
Counter.razor # Can set per-page render mode
MyApp.Client/ # Client project (only if WASM or Auto)
Pages/
Counter.razor # Components that run in browser
Program.cs # WASM entry pointWhen using InteractiveAuto or InteractiveWebAssembly, components that must run in the browser go in the project. Components in the server project run on the server only.
.ClientMyApp/
MyApp/ # 服务器项目
Program.cs # 主机构建器、服务、中间件
Components/
App.razor # 根组件(设置全局渲染模式)
Routes.razor # 路由组件
Layout/
MainLayout.razor # 主布局
Pages/
Home.razor # 默认Static SSR
Counter.razor # 可设置按页面渲染模式
MyApp.Client/ # 客户端项目(仅当使用WASM或Auto模式时存在)
Pages/
Counter.razor # 在浏览器中运行的组件
Program.cs # WASM入口点当使用InteractiveAuto或InteractiveWebAssembly模式时,必须在浏览器中运行的组件应放在项目中。服务器项目中的组件仅在服务器端运行。
.ClientBlazor Hybrid Setup (MAUI)
Blazor Hybrid搭建(MAUI)
xml
<!-- .csproj for MAUI Blazor Hybrid -->
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
</PropertyGroup>
</Project>csharp
// MainPage.xaml.cs hosts BlazorWebView
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}xml
<!-- MainPage.xaml -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:b="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui">
<b:BlazorWebView HostPage="wwwroot/index.html">
<b:BlazorWebView.RootComponents>
<b:RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" />
</b:BlazorWebView.RootComponents>
</b:BlazorWebView>
</ContentPage>xml
<!-- MAUI Blazor Hybrid的.csproj文件 -->
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
</PropertyGroup>
</Project>csharp
// MainPage.xaml.cs托管BlazorWebView
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}xml
<!-- MainPage.xaml -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:b="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui">
<b:BlazorWebView HostPage="wwwroot/index.html">
<b:BlazorWebView.RootComponents>
<b:RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" />
</b:BlazorWebView.RootComponents>
</b:BlazorWebView>
</ContentPage>Routing
路由
Basic Routing
基础路由
razor
@page "/products"
@page "/products/{Category}"
<h1>Products</h1>
@if (!string.IsNullOrEmpty(Category))
{
<p>Category: @Category</p>
}
@code {
[Parameter]
public string? Category { get; set; }
}razor
@page "/products"
@page "/products/{Category}"
<h1>产品</h1>
@if (!string.IsNullOrEmpty(Category))
{
<p>分类:@Category</p>
}
@code {
[Parameter]
public string? Category { get; set; }
}Route Constraints
路由约束
razor
@page "/products/{Id:int}"
@page "/orders/{Date:datetime}"
@page "/search/{Query:minlength(3)}"
@code {
[Parameter] public int Id { get; set; }
[Parameter] public DateTime Date { get; set; }
[Parameter] public string Query { get; set; } = "";
}razor
@page "/products/{Id:int}"
@page "/orders/{Date:datetime}"
@page "/search/{Query:minlength(3)}"
@code {
[Parameter] public int Id { get; set; }
[Parameter] public DateTime Date { get; set; }
[Parameter] public string Query { get; set; } = "";
}Query String Parameters
查询字符串参数
razor
@page "/search"
@code {
[SupplyParameterFromQuery]
public string? Term { get; set; }
[SupplyParameterFromQuery(Name = "page")]
public int CurrentPage { get; set; } = 1;
}razor
@page "/search"
@code {
[SupplyParameterFromQuery]
public string? Term { get; set; }
[SupplyParameterFromQuery(Name = "page")]
public int CurrentPage { get; set; } = 1;
}NavigationManager
NavigationManager
csharp
@inject NavigationManager Navigation
// Programmatic navigation
Navigation.NavigateTo("/products/electronics");
// With query string
Navigation.NavigateTo("/search?term=keyboard&page=2");
// Force full page reload (bypasses enhanced navigation)
Navigation.NavigateTo("/external-page", forceLoad: true);csharp
@inject NavigationManager Navigation
// 编程式导航
Navigation.NavigateTo("/products/electronics");
// 带查询字符串
Navigation.NavigateTo("/search?term=keyboard&page=2");
// 强制全页重载(绕过增强型导航)
Navigation.NavigateTo("/external-page", forceLoad: true);Enhanced Navigation (.NET 8+)
增强型导航(.NET 8+)
Enhanced navigation intercepts link clicks and form submissions to update only the changed DOM content, preserving page state and avoiding full page reloads. This applies to Static SSR and prerendered pages.
增强型导航会拦截链接点击和表单提交,仅更新DOM中变化的内容,保留页面状态并避免全页重载。这适用于Static SSR和预渲染页面。
How It Works
工作原理
- User clicks a link within the Blazor app
- Blazor intercepts the navigation
- A fetch request loads the new page content
- Blazor patches the DOM with only the differences
- Scroll position and focus state are preserved
- 用户点击Blazor应用内的链接
- Blazor拦截导航请求
- 通过fetch请求加载新页面内容
- Blazor仅用差异内容修补DOM
- 滚动位置和焦点状态得以保留
Opting Out
退出增强型导航
razor
<!-- Disable enhanced navigation for a specific link -->
<a href="/legacy-page" data-enhance-nav="false">Legacy Page</a>
<!-- Disable enhanced form handling for a specific form -->
<form method="post" data-enhance="false">
...
</form>Gotcha: Enhanced navigation may interfere with third-party JavaScript libraries that expect full page loads. Use on links that navigate to pages with JS that initializes on .
data-enhance-nav="false"DOMContentLoadedrazor
<!-- 为特定链接禁用增强型导航 -->
<a href="/legacy-page" data-enhance-nav="false">传统页面</a>
<!-- 为特定表单禁用增强型表单处理 -->
<form method="post" data-enhance="false">
...
</form>注意事项: 增强型导航可能会与依赖全页重载的第三方JavaScript库冲突。对于导航到包含在时初始化的JS的页面,使用属性。
DOMContentLoadeddata-enhance-nav="false"Streaming Rendering (.NET 8+)
流式渲染(.NET 8+)
Streaming rendering sends initial HTML immediately (with placeholder content), then streams updates as async operations complete. Useful for pages with slow data sources.
razor
@page "/dashboard"
@attribute [StreamRendering]
<h1>Dashboard</h1>
@if (orders is null)
{
<p>Loading orders...</p>
}
else
{
<table>
@foreach (var order in orders)
{
<tr><td>@order.Id</td><td>@order.Total</td></tr>
}
</table>
}
@code {
private List<OrderDto>? orders;
protected override async Task OnInitializedAsync()
{
// Initial HTML sent immediately with "Loading orders..."
// Updated HTML streamed when this completes
orders = await OrderService.GetRecentOrdersAsync();
}
}Behavior per render mode:
- Static SSR: Streaming rendering sends the initial response, then patches the DOM via chunked transfer encoding. The page is not interactive.
- InteractiveServer/WebAssembly/Auto: Streaming rendering is less impactful because components re-render automatically after async operations. The attribute primarily benefits the prerender phase.
[StreamRendering]
流式渲染会立即发送初始HTML(包含占位内容),然后在异步操作完成时流式传输更新内容。适用于数据源加载缓慢的页面。
razor
@page "/dashboard"
@attribute [StreamRendering]
<h1>仪表盘</h1>
@if (orders is null)
{
<p>加载订单中...</p>
}
else
{
<table>
@foreach (var order in orders)
{
<tr><td>@order.Id</td><td>@order.Total</td></tr>
}
</table>
}
@code {
private List<OrderDto>? orders;
protected override async Task OnInitializedAsync()
{
// 立即发送包含"加载订单中..."的初始HTML
// 此操作完成后流式传输更新后的HTML
orders = await OrderService.GetRecentOrdersAsync();
}
}各渲染模式下的行为:
- Static SSR: 流式渲染发送初始响应,然后通过分块传输编码修补DOM。页面无交互能力。
- InteractiveServer/WebAssembly/Auto: 流式渲染的影响较小,因为组件会在异步操作完成后自动重新渲染。属性主要在预渲染阶段发挥作用。
[StreamRendering]
AOT-Safe Patterns
AOT安全设计模式
When targeting Blazor WebAssembly with Native AOT (ahead-of-time compilation) or IL trimming, avoid patterns that rely on runtime reflection.
当以Blazor WebAssembly为目标并使用Native AOT(提前编译)或IL裁剪时,避免依赖运行时反射的设计模式。
Source-Generator-First Serialization
优先使用源生成器序列化
csharp
// CORRECT: Source-generated JSON serialization (AOT-compatible)
[JsonSerializable(typeof(ProductDto))]
[JsonSerializable(typeof(List<ProductDto>))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public partial class AppJsonContext : JsonSerializerContext { }
// Register in Program.cs
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
// Usage in HttpClient calls
var products = await Http.GetFromJsonAsync<List<ProductDto>>(
"/api/products",
AppJsonContext.Default.ListProductDto);csharp
// WRONG: Reflection-based serialization (fails under AOT/trimming)
var products = await Http.GetFromJsonAsync<List<ProductDto>>("/api/products");csharp
// 正确:源生成的JSON序列化(兼容AOT)
[JsonSerializable(typeof(ProductDto))]
[JsonSerializable(typeof(List<ProductDto>))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public partial class AppJsonContext : JsonSerializerContext { }
// 在Program.cs中注册
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
// 在HttpClient调用中使用
var products = await Http.GetFromJsonAsync<List<ProductDto>>(
"/api/products",
AppJsonContext.Default.ListProductDto);csharp
// 错误:基于反射的序列化(在AOT/裁剪环境下会失败)
var products = await Http.GetFromJsonAsync<List<ProductDto>>("/api/products");Trim-Safe JS Interop
裁剪安全的JS互操作
csharp
// CORRECT: Use IJSRuntime with explicit method names (no dynamic dispatch)
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "key", "value");
var value = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "key");
// CORRECT: Use IJSObjectReference for module imports (.NET 8+)
var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./js/chart.js");
await module.InvokeVoidAsync("initChart", elementRef, data);
await module.DisposeAsync();csharp
// WRONG: Dynamic dispatch via reflection (trimmed away)
// var method = typeof(JSRuntime).GetMethod("InvokeAsync");
// method.MakeGenericMethod(returnType).Invoke(...)csharp
// 正确:使用IJSRuntime并指定明确的方法名(无动态调度)
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "key", "value");
var value = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "key");
// 正确:使用IJSObjectReference导入模块(.NET 8+)
var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./js/chart.js");
await module.InvokeVoidAsync("initChart", elementRef, data);
await module.DisposeAsync();csharp
// 错误:通过反射进行动态调度(会被裁剪掉)
// var method = typeof(JSRuntime).GetMethod("InvokeAsync");
// method.MakeGenericMethod(returnType).Invoke(...)Linker Configuration
链接器配置
xml
<!-- Preserve types used dynamically in components -->
<ItemGroup>
<TrimmerRootAssembly Include="MyApp.Client" />
</ItemGroup>For types that must be preserved from trimming:
csharp
// Mark types that are accessed via reflection
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class DynamicFormModel
{
// Properties discovered at runtime for form generation
public string Name { get; set; } = "";
public int Age { get; set; }
}xml
<!-- 保留组件中动态访问的类型 -->
<ItemGroup>
<TrimmerRootAssembly Include="MyApp.Client" />
</ItemGroup>对于必须保留以避免被裁剪的类型:
csharp
// 标记通过反射访问的类型
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class DynamicFormModel
{
// 运行时发现的表单生成属性
public string Name { get; set; } = "";
public int Age { get; set; }
}Anti-Patterns to Avoid
应避免的反模式
- Reflection-based DI -- Do not use or
Activator.CreateInstanceto resolve services. Use the built-in DI container with explicit registrations.Type.GetType - Dynamic type loading -- Do not use or
Assembly.Loadat runtime. Register all types at startup.Assembly.GetTypes() - Runtime code generation -- Do not use or
System.Reflection.Emit. Use source generators instead.System.Linq.Expressions.Expression.Compile() - Untyped JSON deserialization -- Do not use without a
JsonSerializer.Deserialize<T>(json). Always provide a source-generated context.JsonSerializerContext
- 基于反射的依赖注入——不要使用或
Activator.CreateInstance解析服务。使用内置的DI容器并进行显式注册。Type.GetType - 动态类型加载——不要在运行时使用或
Assembly.Load。在启动时注册所有类型。Assembly.GetTypes() - 运行时代码生成——不要使用或
System.Reflection.Emit。改用源生成器。System.Linq.Expressions.Expression.Compile() - 无类型JSON反序列化——不要在不提供的情况下使用
JsonSerializerContext。始终提供源生成的上下文。JsonSerializer.Deserialize<T>(json)
Prerendering
预渲染
Prerendering generates HTML on the server before the interactive runtime loads. This improves perceived performance and SEO.
预渲染会在交互式运行时加载前在服务器上生成HTML。这能提升感知性能和SEO效果。
Prerender with Interactive Modes
结合交互式模式预渲染
razor
<!-- Component prerenders on server, then becomes interactive -->
<Counter @rendermode="InteractiveServer" />By default, interactive components prerender. To disable:
razor
@rendermode @(new InteractiveServerRenderMode(prerender: false))razor
<!-- 组件在服务器上预渲染,之后变为交互式 -->
<Counter @rendermode="InteractiveServer" />默认情况下,交互式组件会预渲染。要禁用预渲染:
razor
@rendermode @(new InteractiveServerRenderMode(prerender: false))Persisting State Across Prerender
跨预渲染阶段保留状态
State computed during prerendering is lost when the component reinitializes interactively. Use to preserve it:
PersistentComponentStaterazor
@inject PersistentComponentState ApplicationState
@implements IDisposable
@code {
private List<ProductDto>? products;
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PersistState);
if (!ApplicationState.TryTakeFromJson<List<ProductDto>>(
"products", out var restored))
{
products = await ProductService.GetProductsAsync();
}
else
{
products = restored;
}
}
private Task PersistState()
{
ApplicationState.PersistAsJson("products", products);
return Task.CompletedTask;
}
public void Dispose() => _subscription.Dispose();
}预渲染期间计算的状态会在组件交互式重新初始化时丢失。使用来保留状态:
PersistentComponentStaterazor
@inject PersistentComponentState ApplicationState
@implements IDisposable
@code {
private List<ProductDto>? products;
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PersistState);
if (!ApplicationState.TryTakeFromJson<List<ProductDto>>(
"products", out var restored))
{
products = await ProductService.GetProductsAsync();
}
else
{
products = restored;
}
}
private Task PersistState()
{
ApplicationState.PersistAsJson("products", products);
return Task.CompletedTask;
}
public void Dispose() => _subscription.Dispose();
}.NET 10 Stable Features
.NET 10稳定版特性
These features are available when TFM is detected. They are stable and require no preview opt-in.
net10.0当检测到目标框架时,这些特性可用。它们是稳定版,无需启用预览选项。
net10.0WebAssembly Preloading
WebAssembly预加载
.NET 10 adds preloading of WebAssembly assemblies during the Server phase of InteractiveAuto. When the user first loads a page, the WASM runtime and app assemblies download in the background while the Server circuit handles interactivity. Subsequent navigations switch to WASM faster because assemblies are already cached.
blazor.web.jsrazor
<!-- No code changes needed -- preloading is automatic in .NET 10 -->
<!-- Verify in browser DevTools Network tab: assemblies download during Server phase -->.NET 10在InteractiveAuto的Server阶段通过预加载WebAssembly程序集。当用户首次加载页面时,WASM运行时和应用程序集在后台下载,同时Server回路处理交互。后续导航切换到WASM的速度更快,因为程序集已被缓存。
blazor.web.jsrazor
<!-- 无需修改代码——.NET 10中预加载是自动的 -->
<!-- 在浏览器开发者工具的网络标签中验证:程序集在Server阶段下载 -->Enhanced Form Validation
增强型表单验证
.NET 10 extends validation with improved error message formatting and support for in Static SSR forms. Validation messages render correctly with enhanced form handling ( attribute) without requiring a full page reload.
EditFormIValidatableObjectEnhancecsharp
// IValidatableObject works in Static SSR enhanced forms in .NET 10
public sealed class OrderModel : IValidatableObject
{
[Required]
public string ProductId { get; set; } = "";
[Range(1, 100)]
public int Quantity { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (ProductId == "DISCONTINUED" && Quantity > 0)
{
yield return new ValidationResult(
"Cannot order discontinued products",
[nameof(ProductId), nameof(Quantity)]);
}
}
}.NET 10扩展了验证功能,改进了错误消息格式,并支持在Static SSR表单中使用。验证消息可通过增强型表单处理(属性)正确渲染,无需全页重载。
EditFormIValidatableObjectEnhancecsharp
// .NET 10中,IValidatableObject可在Static SSR增强型表单中正常工作
public sealed class OrderModel : IValidatableObject
{
[Required]
public string ProductId { get; set; } = "";
[Range(1, 100)]
public int Quantity { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (ProductId == "DISCONTINUED" && Quantity > 0)
{
yield return new ValidationResult(
"无法订购已停产产品",
[nameof(ProductId), nameof(Quantity)]);
}
}
}Blazor Diagnostics Middleware
Blazor诊断中间件
.NET 10 adds middleware for inspecting Blazor circuit and component state in development:
MapBlazorDiagnosticscsharp
// Program.cs -- available in .NET 10
if (app.Environment.IsDevelopment())
{
app.MapBlazorDiagnostics(); // Exposes /_blazor/diagnostics endpoint
}The diagnostics endpoint shows active circuits, component tree, render mode assignments, and timing data. Use it to debug render mode boundaries and component lifecycle issues during development.
.NET 10新增中间件,用于在开发环境中检查Blazor回路和组件状态:
MapBlazorDiagnosticscsharp
// Program.cs——.NET 10中可用
if (app.Environment.IsDevelopment())
{
app.MapBlazorDiagnostics(); // 暴露/_blazor/diagnostics端点
}诊断端点会显示活跃回路、组件树、渲染模式分配和计时数据。在开发期间,使用它来调试渲染模式边界和组件生命周期问题。
Agent Gotchas
注意事项
- Do not default to InteractiveServer for every page. Static SSR is the default and most efficient render mode. Only add interactivity where user interaction requires it.
- Do not put WASM-targeted components in the server project. Components that must run in the browser (InteractiveWebAssembly or InteractiveAuto) belong in the project.
.Client - Do not forget when prerendering. Without it, data fetched during prerender is discarded and re-fetched when the component becomes interactive, causing a visible flicker.
PersistentComponentState - Do not use reflection-based serialization in WASM. Always use with source-generated serializers for AOT compatibility and trimming safety.
JsonSerializerContext - Do not force-load navigation unless leaving the Blazor app. bypasses enhanced navigation and causes a full page reload.
NavigateTo("/page", forceLoad: true) - Do not nest interactive render modes incorrectly. A child component cannot request a more interactive mode than its parent. Plan render mode boundaries from the layout down.
- 不要默认对所有页面使用InteractiveServer模式。Static SSR是默认且最高效的渲染模式。仅在用户交互需要时添加交互性。
- 不要将WASM目标组件放在服务器项目中。必须在浏览器中运行的组件(InteractiveWebAssembly或InteractiveAuto模式)应放在项目中。
.Client - 预渲染时不要忘记使用PersistentComponentState。如果不使用它,预渲染期间获取的数据会被丢弃,并在组件变为交互式时重新获取,导致可见的闪烁。
- 不要在WASM中使用基于反射的序列化。始终结合源生成序列化器使用,以确保AOT兼容性和裁剪安全性。
JsonSerializerContext - 除非离开Blazor应用,否则不要强制重载导航。会绕过增强型导航并导致全页重载。
NavigateTo("/page", forceLoad: true) - 不要错误嵌套交互式渲染模式。子组件无法请求比父组件更高的交互模式。从布局开始规划渲染模式边界。
Prerequisites
前置条件
- .NET 8.0+ (Blazor Web App template, render modes, enhanced navigation, streaming rendering)
- .NET 10.0 for stable enhanced features (WebAssembly preloading, enhanced form validation, diagnostics middleware)
- MAUI workload for Blazor Hybrid ()
dotnet workload install maui
- .NET 8.0+(Blazor Web App模板、渲染模式、增强型导航、流式渲染)
- .NET 10.0(稳定版增强特性:WebAssembly预加载、增强型表单验证、诊断中间件)
- MAUI工作负载(用于Blazor Hybrid,执行安装)
dotnet workload install maui