今天聊一个选型话题。新项目到底选 Blazor 还是 Razor Pages?我两种都试过,交过学费,今天说点不绕弯的结论。


先说结论

别二选一。混合用。 核心管理后台用 Blazor Server,对外简单页面用 Razor Pages。这不是骑墙,是秦巴牧云项目用血泪换出来的经验。

下面讲讲为什么,以及我交了多少学费。

当初为什么全选了 Blazor

2024 年初启动秦巴牧云项目。一个养殖场管理系统,管理端有库存管理、饲料配方、批次追溯、报表统计,对外有供应商对账页面和饲养员小程序的 WebView 页。

当时我被 Blazor Server 的卖点打动了:

不用写 JavaScript。C# 全栈,前后端一个项目,组件复用,SignalR 实时通信。场长要看实时库存?一个 @onclick 搞定,不用手写 AJAX。

听起来太香了。我把整个项目都建在了 Blazor Server 上。管理端、供应商页面、饲养员 WebView,全是 .razor 文件。

三个月后我开始后悔。

学费一:供应商页面根本不需要 Blazor

供应商对账页面长什么样?一个表单:选供应商、选月份、点查询、看表格。没有实时更新,没有复杂交互,没有拖拽。

用 Razor Pages 写,就是一个 GET 请求渲染页面,一个 POST 请求提交查询,返回带数据的页面。完事。

用 Blazor Server 写呢?页面打开时建立 SignalR 长连接,服务端维护一个组件树,用户点「查询」时通过 WebSocket 发一个事件到服务端,服务端重新渲染组件,把 diff 通过 WebSocket 推回客户端。

为了一个查询按钮,我在服务端维护了一个 WebSocket 连接。供应商有 30 多家,同时在线看对账的可能就三五个人,但这三五个人各占一个连接,各占服务端内存。

这就是杀鸡用牛刀。Blazor 的交互能力对这种页面是浪费,但它的连接开销是实打实的成本。

学费二:饲养员 WebView 里 Blazor Server 翻车

饲养员在小程序 WebView 里打开 Blazor 页面。养殖场在山里,4G 信号不稳定。

Blazor Server 的致命弱点:每次交互都要走网络。 点一个按钮、切一个 tab、展开一个折叠面板,都得通过 SignalR 发到服务端再推回来。信号差的时候,点一下等三秒才响应。饲养员的反馈是「这破系统卡死了」。

Razor Pages 不一样。页面是服务端渲染好整个 HTML 一次性发过来的。打开页面就完了,后续点链接是另一个 HTTP 请求,浏览器原生的加载体验,不会卡在「等 WebSocket 回包」上。

更关键的是,SignalR 连接在弱网下断了还得重连。重连期间整个页面不响应。饲养员不知道页面断了,点哪都没反应,以为系统坏了。

最后饲养员 WebView 那几个页面,我全用 Razor Pages 重写了。一个 .cshtml + 一个 .cshtml.cs,简单粗暴,稳定如老狗。

学费三:简单 CRUD 页面 Blazor 更啰嗦

一个标准的列表+新增+编辑+删除页面,Razor Pages 的写法:

Razor Pages 写法:

// Pages/FeedList.cshtml.cs
public class FeedListModel : PageModel
{
    private readonly AppDb db;
    public List<FeedStock> Items { get; set; }

    public FeedListModel(AppDb db) => this.db = db;

    public async Task OnGetAsync() =>
        Items = await db.FeedStocks.ToListAsync();
}
<!-- Pages/FeedList.cshtml -->
@page
@model FeedListModel
<table>
  @foreach (var f in Model.Items) { <tr>...</tr> }
</table>

一个文件搞完。逻辑在 PageModel,视图在 cshtml,HTTP 语义在 @page 指令里自动处理。

同样的页面用 Blazor 写,你得处理:组件生命周期 OnInitializedAsync、状态管理(数据列表放哪、刷新怎么触发)、导航(NavigationManager)、表单验证(EditForm + DataAnnotationsValidator)。

不是说 Blazor 做不了,而是对简单页面来说,这些是多余的心智负担。 Razor Pages 的 PageModel 天然对应一个 HTTP 请求的生命周期,语义清晰。Blazor 的组件生命周期是为复杂交互设计的,对简单 CRUD 用不上。

秦巴牧云有十几个「列表+表单」页面,全用 Blazor 写的话,每个都得多写三分之一的胶水代码。不划算。

Blazor 的主场在哪里

说完了学费,说说 Blazor 真正不可替代的场景。这些是 Razor Pages 做不到或者做起来很丑的:

场景一:实时看板。 场长要看实时库存、当日领料统计、各圈舍存栏量。数据每隔几秒更新。Blazor Server 用 Timer + StateHasChanged + IAsyncEnumerable,服务端推着 UI 更新。Razor Pages 做这个得手写 JavaScript + SignalR + DOM 操作,回到了前端三件套时代。

场景二:复杂交互表单。 饲料配方页面:选一个原料,自动填充营养成分;改一个配比,实时重算总价和蛋白含量;拖拽排序原料顺序。这种富交互用 Blazor 的组件状态 + 事件绑定写起来顺手,C# 一把梭。Razor Pages 做这个得写大量 JavaScript。

场景三:多步骤向导。 批次追溯录入:五步表单,每步有依赖关系(选了品种才能选圈舍,选了圈舍才能选饲料配方)。Blazor 的组件树天然适合这种有状态的向导。Razor Pages 做多步向导要么用 session 存中间状态,要么每步提交到服务端再渲染回来,体验割裂。

这三个场景是 Blazor 的主场。在这些地方,Blazor 省下的 JavaScript 代码量是实打实的。

秦巴牧云最终的混合架构

                        ┌─────────────────────────────────┐
                        │          共享层                  │
                        │  Domain · EF Core · Service · Auth│
                        └────────┬──────────────┬─────────┘
                                 │引用          │引用
                    ┌────────────┴──┐  ┌───────┴──────────┐
  场长/会计 ─内网──→ │ Blazor Server  │  │  Razor Pages     │ ←外网/弱网─ 供应商
                    │  (管理后台)   │  │  (简单页面)      │ ←外网/弱网─ 饲养员
                    │                │  │                  │
                    │ 实时看板       │  │ 供应商对账        │
                    │ 配方向导       │  │ 饲养员 WebView    │
                    │ 批次追溯       │  │ 列表+表单         │
                    │                │  │                  │
                    │ SignalR长连接  │  │ HTTP请求-响应     │
                    │ 组件状态       │  │ 服务端渲染        │
                    └────────────────┘  └──────────────────┘

  同一个 ASP.NET Core 项目
  /admin 路由走 Blazor,/portal 路由走 Razor Pages
  共享 Domain + EF Core + Service 层,不重复写业务逻辑
  Program.cs 里同时注册:MapBlazor + MapRazorPages

这不是两个项目,是同一个 ASP.NET Core 项目。在 Program.cs 里同时注册两套端点:

var builder = WebApplication.CreateBuilder(args);

// Blazor Server 注册
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

var app = builder.Build();

// Razor Pages 路由(供应商、饲养员页面)
app.MapRazorPages();

// Blazor Server 路由(管理后台)
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

/FeedList 走 Razor Pages,/admin/dashboard 走 Blazor。共享同一套 Domain、EF Core、Service 层,不重复写业务逻辑。

这是 .NET 全栈的优势——一个项目里两种 UI 模式共存,想用哪个用哪个,不用切框架。

怎么选:四条判断线

判断维度 选 Razor Pages 选 Blazor Server
交互复杂度 列表+表单,点链接跳页 拖拽、实时更新、多步向导
网络环境 弱网、移动端 WebView 内网、稳定宽带
并发用户数 不确定/波动大 可控(几十到几百)
JavaScript 量 几乎没有就用 JS 否则得写一堆 JS

四条里命中两条以上,方向就明确了。不确定的页面,先按 Razor Pages 起步——它简单、稳定、好调试。后续发现交互需求上来了,再迁移到 Blazor。反过来不行——从 Blazor 迁回 Razor Pages 我干过,重写的痛苦不想再来一次。

所以我的建议是:默认 Razor Pages,遇到 Blazor 主场场景时再用 Blazor。 这是增量引入,不是全量押注。


最后

选型这事没有银弹。Blazor 不是银弹,Razor Pages 也不是。能同时在一个项目里用两个,才是 .NET 全栈真正的优势。秦巴牧云的学费,希望帮你省点。

一句话总结:默认 Razor Pages,遇到实时看板/复杂交互/多步向导再上 Blazor Server,一个项目里混着用,共享 Domain 层。

你项目里用的是 Blazor 还是 Razor Pages?有没有踩过类似的坑?

转发给还在纠结选型的同事,少走我们趟过的弯路。

关注 CSharp精选营,每周二四 get 能直接抄的 C# 实战。


关于作者

码农刚子,六年 ERP / 制造业系统开发,专注 C# / .NET / Blazor。

博客:https://www.coderlog.net/ 公众号:CSharp精选营