dotnetBlazor模式设计 dotnet-blazor-patterns

这个技能专注于dotnet Blazor应用的架构设计,涵盖托管模型、渲染模式、路由、流式渲染、预渲染和AOT安全模式。它帮助开发者高效构建交互式Web应用,适用于前端开发、Web开发、.NET开发和Blazor框架。关键词:Blazor、dotnet、Web开发、前端、渲染模式、托管模型、路由、AOT安全、.NET 10、增强导航。

前端开发 0 次安装 0 次浏览 更新于 3/6/2026

名称: dotnet-blazor-patterns 描述: “构建Blazor应用。托管模型、渲染模式、路由、流式渲染、预渲染。” 用户可调用: false

dotnet-blazor-模式

Blazor托管模型、渲染模式、项目设置、路由、增强导航、流式渲染和AOT安全模式。涵盖所有五种托管模型(InteractiveServer、InteractiveWebAssembly、InteractiveAuto、Static SSR、Hybrid),并提供每种模型的权衡分析。

范围

  • Blazor Web应用项目设置和配置
  • 托管模型选择(Server、WASM、Auto、SSR、Hybrid)
  • 渲染模式配置(全局、每页、每组件)
  • 路由和增强导航
  • 流式渲染和预渲染
  • AOT安全Blazor模式

范围外

  • 组件架构(生命周期、状态、JS互操作)–参见[技能:dotnet-blazor-components]
  • 跨托管模型的身份验证–参见[技能:dotnet-blazor-auth]
  • bUnit组件测试–参见[技能:dotnet-blazor-testing]
  • 独立SignalR模式–参见[技能:dotnet-realtime-communication]
  • 基于浏览器的端到端测试–参见[技能:dotnet-playwright]
  • UI框架选择决策树–参见[技能:dotnet-ui-chooser]

交叉引用:[技能:dotnet-blazor-components]用于组件架构,[技能:dotnet-blazor-auth]用于身份验证,[技能:dotnet-blazor-testing]用于bUnit测试,[技能:dotnet-realtime-communication]用于独立SignalR,[技能:dotnet-playwright]用于端到端测试,[技能:dotnet-ui-chooser]用于框架选择,[技能:dotnet-accessibility]用于无障碍模式(ARIA、键盘导航、屏幕阅读器)。


托管模型与渲染模式

Blazor Web应用(.NET 8+)是默认项目模板,取代了独立的Blazor Server和Blazor WebAssembly模板。渲染模式可以全局设置、每页设置或每组件设置。

渲染模式概述

渲染模式 属性 交互性 连接 最佳用途
静态SSR (无/默认) 无–服务器渲染HTML,无交互性 仅HTTP请求 内容页面、SEO、交互性最小的表单
InteractiveServer @rendermode InteractiveServer 完整 SignalR电路 低延迟交互性、完全服务器访问、小用户群
InteractiveWebAssembly @rendermode InteractiveWebAssembly 完整(下载后) 无(在浏览器中运行) 离线能力、大用户群、减少服务器负载
InteractiveAuto @rendermode InteractiveAuto 完整 初始SignalR,然后WASM 最佳结合–即时交互性,最终客户端侧
Blazor Hybrid MAUI/WPF/WinForms中的BlazorWebView 完整(原生) 无(在进程中运行) 具有Web UI的桌面/移动应用,原生API访问

每种模式的权衡

关注点 静态SSR InteractiveServer InteractiveWebAssembly InteractiveAuto Hybrid
首次加载 快速 快速 慢(WASM下载) 快速(服务器优先) 即时(本地)
服务器资源 最小 每用户电路 下载后无 电路然后无
离线支持 部分
完全.NET API访问 是(服务器) 是(服务器) 有限(浏览器沙盒) 按阶段变化 是(原生)
可扩展性 受电路限制 高(WASM后) 不适用(本地)
SEO 预渲染 预渲染 预渲染 不适用

设置渲染模式

全局(App.razor):

<!-- 为所有页面设置默认渲染模式 -->
<Routes @rendermode="InteractiveServer" />

每页:

@page "/dashboard"
@rendermode InteractiveServer

<h1>仪表板</h1>

每组件:

<Counter @rendermode="InteractiveWebAssembly" />

注意事项: 没有显式的渲染模式边界时,子组件不能请求比其父组件更交互的渲染模式。然而,支持交互式岛屿:您可以在静态SSR页面上嵌入的组件上放置@rendermode属性来创建渲染模式边界,从而在静态内容下启用交互式子组件。


项目设置

Blazor Web应用(默认模板)

# 使用InteractiveServer渲染模式创建Blazor Web应用
dotnet new blazor -n MyApp

# 带特定交互性选项
dotnet 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    # 仅静态SSR

Blazor Web应用项目结构

MyApp/
  MyApp/                     # 服务器项目
    Program.cs               # 主机构建器、服务、中间件
    Components/
      App.razor              # 根组件(设置全局渲染模式)
      Routes.razor           # 路由器组件
      Layout/
        MainLayout.razor     # 主布局
      Pages/
        Home.razor            # 默认静态SSR
        Counter.razor         # 可设置每页渲染模式
  MyApp.Client/              # 客户端项目(仅当WASM或Auto时)
    Pages/
      Counter.razor           # 在浏览器中运行的组件
    Program.cs                # WASM入口点

使用InteractiveAuto或InteractiveWebAssembly时,必须在浏览器中运行的组件放入.Client项目。服务器项目中的组件仅在服务器上运行。

Blazor Hybrid设置(MAUI)

<!-- 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>
// MainPage.xaml.cs托管BlazorWebView
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}
<!-- 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>

路由

基本路由

@page "/products"
@page "/products/{Category}"

<h1>产品</h1>
@if (!string.IsNullOrEmpty(Category))
{
    <p>类别: @Category</p>
}

@code {
    [Parameter]
    public string? Category { get; set; }
}

路由约束

@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; } = "";
}

查询字符串参数

@page "/search"

@code {
    [SupplyParameterFromQuery]
    public string? Term { get; set; }

    [SupplyParameterFromQuery(Name = "page")]
    public int CurrentPage { get; set; } = 1;
}

NavigationManager

@inject NavigationManager Navigation

// 程序化导航
Navigation.NavigateTo("/products/electronics");

// 带查询字符串
Navigation.NavigateTo("/search?term=keyboard&page=2");

// 强制完整页面重载(绕过增强导航)
Navigation.NavigateTo("/external-page", forceLoad: true);

增强导航(.NET 8+)

增强导航拦截链接点击和表单提交,以仅更新更改的DOM内容,保留页面状态并避免完整页面重载。这适用于静态SSR和预渲染页面。

工作原理

  1. 用户在Blazor应用内点击链接
  2. Blazor拦截导航
  3. 获取请求加载新页面内容
  4. Blazor仅用差异修补DOM
  5. 滚动位置和焦点状态被保留

退出

<!-- 为特定链接禁用增强导航 -->
<a href="/legacy-page" data-enhance-nav="false">旧页面</a>

<!-- 为特定表单禁用增强表单处理 -->
<form method="post" data-enhance="false">
    ...
</form>

注意事项: 增强导航可能会干扰期望完整页面加载的第三方JavaScript库。在导航到在DOMContentLoaded上初始化的JS页面时,使用data-enhance-nav="false"


流式渲染(.NET 8+)

流式渲染立即发送初始HTML(带有占位符内容),然后在异步操作完成时流式更新。适用于有慢数据源的页面。

@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();
    }
}

每种渲染模式的行为:

  • 静态SSR: 流式渲染发送初始响应,然后通过分块传输编码修补DOM。页面不交互。
  • InteractiveServer/WebAssembly/Auto: 流式渲染影响较小,因为组件在异步操作后自动重新渲染。[StreamRendering]属性主要受益于预渲染阶段。

AOT安全模式

当针对Blazor WebAssembly使用原生AOT(提前编译)或IL修剪时,避免依赖运行时反射的模式。

源生成器优先的序列化

// 正确:源生成的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);
// 错误:基于反射的序列化(AOT/修剪下失败)
var products = await Http.GetFromJsonAsync<List<ProductDto>>("/api/products");

修剪安全的JS互操作

// 正确:使用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();
// 错误:通过反射的动态分派(被修剪掉)
// var method = typeof(JSRuntime).GetMethod("InvokeAsync");
// method.MakeGenericMethod(returnType).Invoke(...)

链接器配置

<!-- 保留在组件中动态使用的类型 -->
<ItemGroup>
  <TrimmerRootAssembly Include="MyApp.Client" />
</ItemGroup>

对于必须从修剪中保留的类型:

// 标记通过反射访问的类型
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class DynamicFormModel
{
    // 运行时发现的表单生成属性
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

避免的反模式

  1. 基于反射的DI – 不要使用Activator.CreateInstanceType.GetType来解析服务。使用内置DI容器和显式注册。
  2. 动态类型加载 – 不要在运行时使用Assembly.LoadAssembly.GetTypes()。在启动时注册所有类型。
  3. 运行时代码生成 – 不要使用System.Reflection.EmitSystem.Linq.Expressions.Expression.Compile()。改用源生成器。
  4. 无类型JSON反序列化 – 不要使用JsonSerializer.Deserialize<T>(json)而不带JsonSerializerContext。始终提供源生成的上下文。

预渲染

预渲染在交互式运行时加载之前在服务器上生成HTML。这提高了感知性能和SEO。

带交互模式的预渲染

<!-- 组件在服务器上预渲染,然后变得交互式 -->
<Counter @rendermode="InteractiveServer" />

默认情况下,交互式组件预渲染。要禁用:

@rendermode @(new InteractiveServerRenderMode(prerender: false))

跨预渲染保留状态

预渲染期间计算的状态在组件重新初始化交互式时丢失。使用PersistentComponentState来保留它:

@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稳定功能

当检测到net10.0 TFM时,这些功能可用。它们是稳定的,不需要预览选择。

WebAssembly预加载

.NET 10在InteractiveAuto的服务器阶段添加blazor.web.js预加载WebAssembly程序集。当用户首次加载页面时,WASM运行时和应用程序集在后台下载,同时服务器电路处理交互性。后续导航更快地切换到WASM,因为程序集已缓存。

<!-- 无需代码更改--预加载在.NET 10中是自动的 -->
<!-- 在浏览器DevTools网络选项卡中验证:程序集在服务器阶段下载 -->

增强表单验证

.NET 10扩展EditForm验证,改进错误消息格式化,并在静态SSR表单中支持IValidatableObject。验证消息与增强表单处理(Enhance属性)正确渲染,无需完整页面重载。

// IValidatableObject在.NET 10的静态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诊断中间件

.NET 10添加MapBlazorDiagnostics中间件,用于在开发中检查Blazor电路和组件状态:

// Program.cs -- 在.NET 10中可用
if (app.Environment.IsDevelopment())
{
    app.MapBlazorDiagnostics(); // 暴露/_blazor/diagnostics端点
}

诊断端点显示活动电路、组件树、渲染模式分配和计时数据。用于调试开发期间的渲染模式边界和组件生命周期问题。


代理注意事项

  1. 不要默认将每个页面设置为InteractiveServer。 静态SSR是默认且最高效的渲染模式。仅在用户交互需要时添加交互性。
  2. 不要把WASM目标组件放在服务器项目中。 必须在浏览器中运行的组件(InteractiveWebAssembly或InteractiveAuto)属于.Client项目。
  3. 预渲染时不要忘记PersistentComponentState 没有它,预渲染期间获取的数据被丢弃,并在组件变得交互式时重新获取,导致可见闪烁。
  4. 不要在WASM中使用基于反射的序列化。 始终使用JsonSerializerContext和源生成的序列化器以确保AOT兼容性和修剪安全。
  5. 除非离开Blazor应用,否则不要强制加载导航。 NavigateTo("/page", forceLoad: true)绕过增强导航并导致完整页面重载。
  6. 不要错误嵌套交互式渲染模式。 子组件不能请求比其父组件更交互的模式。从布局向下规划渲染模式边界。

先决条件

  • .NET 8.0+(Blazor Web应用模板、渲染模式、增强导航、流式渲染)
  • .NET 10.0用于稳定增强功能(WebAssembly预加载、增强表单验证、诊断中间件)
  • MAUI工作负载用于Blazor Hybrid(dotnet workload install maui

参考