关于 convert-blazor-server-to-webapp
该技能指导 Agent 将 .NET 6/7 的 Blazor Server 应用迁移到 .NET 8+ Blazor Web App,同时保留 InteractiveServer 交互模式。内容覆盖组件重组、服务与端点更新,以及迁移验证和常见陷阱。
主要功能
- 01更新目标框架与相关包
- 02将路由内容移至 Routes.razor
- 03把 _Host.cshtml 转为 App.razor
- 04替换 Blazor 服务与端点映射
- 05迁移身份验证状态并验证应用
适用场景
- 01将旧版 Blazor Server 升级到 .NET 8+
- 02保留交互式服务器渲染并采用新应用模型
- 03排查迁移后的防伪中间件或预渲染问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill convert-blazor-server-to-webapp安装后在 Claude Code 中输入/convert-blazor-server-to-webapp确认已加载。
详细介绍
将 Blazor Server 应用转换为 Blazor Web App
此技能可帮助 Agent 将 .NET 8 之前的 Blazor Server 应用转换为 .NET 8+ Blazor Web App。旧托管模型使用 AddServerSideBlazor/MapBlazorHub,并以 _Host.cshtml Razor Page 作为入口。新的 Blazor Web App 模型使用 AddRazorComponents/MapRazorComponents,并以 App.razor 根组件作为入口,从而支持按组件设置渲染模式、增强型导航、流式渲染及其他 .NET 8+ 功能。转换后的应用使用 InteractiveServer 渲染模式,以保留现有的交互行为。
适用场景
- 将 Blazor Server 应用从 .NET 6 或 .NET 7 迁移到 .NET 8+
- 应用当前在
Program.cs(或Startup.cs)中使用AddServerSideBlazor()和MapBlazorHub() - 应用使用
Pages/_Host.cshtml(或_Host.razor)作为宿主页,并使用组件标记帮助程序 - 希望在保留交互式服务器渲染的同时,采用 Blazor Web App 的新功能
不适用场景
- 应用已经使用
AddRazorComponents和MapRazorComponents。 它已经是 Blazor Web App,无需转换。请在此处停止,并告知用户应用已经使用 Blazor Web App 模型。 - Blazor WebAssembly 或托管式 Blazor WebAssembly 应用——这些应用有不同的迁移路径
- 应用应继续使用旧版 Blazor Server 托管模型(只更新 TFM 和包)
- 应用面向 .NET Framework——必须先迁移到 .NET
输入
| 输入 | 必需 | 说明 |
|---|---|---|
| Blazor Server 项目 | 是 | Blazor Server 应用的 .csproj 和源文件 |
| 目标框架 | 是 | .NET 8 或更高版本(例如 net8.0、net9.0、net10.0) |
Program.cs 或 Startup.cs | 是 | 应用的服务和中间件配置 |
_Host.cshtml 位置 | 建议提供 | 通常为 Pages/_Host.cshtml;某些项目中可能是 _Host.razor |
工作流
提交策略: 每完成一个逻辑步骤后都提交一次,以便审查和二分定位迁移问题。
步骤 1:更新项目文件
更新 .csproj 文件:
- 将目标框架名字 (TFM) 更改为目标版本:
<TargetFramework>net8.0</TargetFramework> - 将所有
Microsoft.AspNetCore.*、Microsoft.EntityFrameworkCore.*、Microsoft.Extensions.*和System.Net.Http.Json包引用更新为相应版本。
有关非 Blazor 项目文件的更改(可空引用类型、隐式 using、HTTP/3 支持等),请参阅通用 ASP.NET Core 迁移指南。
步骤 2:从 App.razor 创建 Routes.razor
旧版 App.razor 包含 <Router> 组件。将此内容移至新的 Routes.razor 文件,以便 App.razor 成为根 HTML 文档组件。
- 在项目根目录中创建新文件
Routes.razor。 - 将
App.razor的全部内容移至Routes.razor。 - 如果内容被
<CascadingAuthenticationState>包裹,请移除该包装器(它将在步骤 5 中由服务替代)。 - 保持
App.razor为空,以便进行下一步。
生成的 Routes.razor 应类似于:
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
如果应用使用 <AuthorizeRouteView> 而不是 <RouteView>,请保留它——它在 Blazor Web App 中的工作方式相同。
步骤 3:将 _Host.cshtml 转换为 App.razor
将 HTML 外壳从 Pages/_Host.cshtml 移到现在为空的 App.razor 中,并将其从 Razor 页面转换为 Razor 组件:
-
移除 Razor 页面指令 — 删除
@page "/"、@using Microsoft.AspNetCore.Components.Web、@namespace和@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers。 -
添加组件注入 — 如果使用依赖环境的错误界面,请添加:
@inject IHostEnvironment Env -
修正 base 标记 — 将
<base href="~/" />替换为<base href="/" />。 -
替换 HeadOutlet 组件标记帮助器 — 将:
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />替换为:
<HeadOutlet @rendermode="InteractiveServer" /> -
将 App 组件标记帮助器替换为 Routes — 将:
<component type="typeof(App)" render-mode="ServerPrerendered" />替换为:
<Routes @rendermode="InteractiveServer" /> -
替换 Environment 标记帮助器 — 将:
<environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment>替换为:
@if (Env.IsDevelopment()) { <text> An unhandled exception has occurred. See browser dev tools for details. </text> } else { <text> An error has occurred. This app may no longer respond until reloaded. </text> } -
更新 Blazor 脚本 — 将:
<script src="_framework/blazor.server.js"></script>替换为:
<script src="_framework/blazor.web.js"></script> -
添加渲染模式导入 — 添加到
_Imports.razor:@using static Microsoft.AspNetCore.Components.Web.RenderMode -
删除
Pages/_Host.cshtml(以及可能存在的Pages/_Host.cshtml.cs)。
预渲染说明: 如果原应用使用的是 render-mode="Server"(而非 "ServerPrerendered"),则已禁用预渲染。请使用 new InteractiveServerRenderMode(prerender: false) 替代 InteractiveServer,并将其用于 HeadOutlet 和 Routes,以保留此行为。
步骤 4:更新 Program.cs
对 Program.cs(如果应用采用较旧的托管模式,则为 Startup.cs)进行以下更改:
-
替换 Blazor Server 服务 — 将:
builder.Services.AddServerSideBlazor();替换为:
builder.Services.AddRazorComponents() .AddInteractiveServerComponents();如果
AddServerSideBlazor配置了选项(例如 circuit 选项、hub 选项或详细错误),请将其迁移到AddInteractiveServerComponents:// Old: builder.Services.AddServerSideBlazor(options => { options.DetailedErrors = true; options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); }); // New: builder.Services.AddRazorComponents() .AddInteractiveServerComponents(options => { options.DetailedErrors = true; options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); }); -
替换 Blazor 终结点映射 — 将:
app.MapBlazorHub();替换为:
app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();确保有一条针对项目根命名空间的
using语句,以便App能解析到App.razor组件。 -
移除回退路由 — 删除:
app.MapFallbackToPage("/_Host"); -
移除显式路由中间件 — 如果存在,请删除:
app.UseRouting();终结点路由是默认设置,不再需要显式调用
UseRouting()。 -
添加防伪中间件 — 如果存在
UseAuthentication/UseAuthorization,请在其后添加:app.UseAntiforgery();AddRazorComponents会自动注册防伪服务,但必须在管道中显式添加中间件。否则,表单 POST 请求会因 400 错误而失败。
步骤 5:迁移 CascadingAuthenticationState(如果存在)
如果应用使用 <CascadingAuthenticationState> 包裹路由器:
- 移除
<CascadingAuthenticationState>组件包装器(如果按此工作流操作,已在步骤 2 中完成)。 - 在
Program.cs中添加级联身份验证状态服务:builder.Services.AddCascadingAuthenticationState();
在 Blazor Web Apps 中,组件包装器方式无法跨越渲染模式边界。基于服务的方式会将 Task<AuthenticationState> 作为级联值提供给所有组件,无论其渲染模式如何。
第 6 步:建议的改进(可选)
以下是可选的现代化改进,并非转换成功所必需。如果建议采用其中任何一项,请明确说明这些改进是可选的。
- 将
UseStaticFiles替换为MapStaticAssets(.NET 9+):app.MapStaticAssets()可优化静态文件服务,支持指纹标识、预压缩和基于内容的 ETag。请参阅 MapStaticAssets 文档。 - 对异步加载数据的页面(
OnInitializedAsync),添加@attribute [StreamRendering],以提升感知性能。页面会立即呈现初始同步内容,并在异步数据到达后重新呈现。 - 如果
<link>标签引用了_Host程序集名称,请更新 CSS 隔离包引用,确保其与项目的实际程序集名称一致:<link href="{AssemblyName}.styles.css" rel="stylesheet" />。 - 如需了解其他非 Blazor 改进(最小化托管、HTTP/3、输出缓存等),请参阅通用 ASP.NET Core 迁移指南。
第 7 步:验证迁移
- 以新框架为目标构建项目。确认没有编译错误。
- 搜索是否仍有对已移除 API 的引用:
AddServerSideBlazorMapBlazorHubMapFallbackToPageblazor.server.js_Host.cshtml
- 运行应用并验证:
- 页面能正常加载和呈现
- 交互功能正常(表单、事件处理程序、SignalR 电路)
- 页面间导航正常
- 如有身份验证和授权流程,则确保其正常运行
- 运行现有测试。
验证清单
- 不再引用
AddServerSideBlazor - 不再引用
MapBlazorHub - 不再引用
MapFallbackToPage("/_Host") - 不再引用
blazor.server.js - 已删除
Pages/_Host.cshtml -
App.razor作为根组件,包含完整的 HTML 文档结构 -
Routes.razor包含<Router>配置 -
Program.cs使用AddRazorComponents().AddInteractiveServerComponents() -
Program.cs使用MapRazorComponents<App>().AddInteractiveServerRenderMode() - 中间件管道中包含
app.UseAntiforgery() - 如果应用使用了
<CascadingAuthenticationState>,则已替换为AddCascadingAuthenticationState()服务注册 - 应用在目标框架上成功构建并运行
常见陷阱
| 陷阱 | 解决方案 |
|---|---|
缺少 UseAntiforgery() 中间件 | AddRazorComponents 会注册防伪服务,但必须显式添加中间件。将 app.UseAntiforgery() 放在 UseAuthentication/UseAuthorization 之后。否则,表单 POST 请求会失败并返回 400 错误。 |
忘记将 blazor.server.js 替换为 blazor.web.js | 旧脚本不适用于 Blazor Web App 模型。将所有对 _framework/blazor.server.js 的引用替换为 _framework/blazor.web.js。 |
未移除 <CascadingAuthenticationState> 包装器 | 在 Blazor Web Apps 中,该组件包装器无法跨越呈现模式边界。请改用 builder.Services.AddCascadingAuthenticationState()。 |
在中间件管道中保留 app.UseRouting() | 不再需要显式调用 UseRouting(),而且它可能会干扰端点路由。除非其他中间件有特殊需要,否则请将其移除。 |
在禁用预呈现时使用 InteractiveServer | 如果原始应用使用的是 render-mode="Server"(而不是 "ServerPrerendered"),请使用 new InteractiveServerRenderMode(prerender: false) 以保持相同的行为。使用 InteractiveServer 会启用预呈现,这可能会导致初始化时依赖 JS 互操作的组件出现意外问题。 |
未迁移 AddServerSideBlazor 电路选项 | 如果配置了电路选项、中心选项或详细错误设置,请将它们迁移到 AddInteractiveServerComponents(options => { ... })。否则,这些设置会被静默丢弃。 |
将 UseAntiforgery() 放在身份验证中间件之前 | 防伪中间件必须放在 UseAuthentication 和 UseAuthorization 之后。若将其放在之前,防伪验证会在用户身份建立之前运行。 |
| CSS 隔离样式包链接使用了错误的程序集名称 | 如果 <link href="{Name}.styles.css"> 标签引用的是旧项目名称,请将其更新为当前程序集名称。 |
更多信息
- 将 Blazor Server 应用转换为 Blazor Web App — 官方分步迁移指南
- ASP.NET Core Blazor 呈现模式 — 了解 InteractiveServer、InteractiveWebAssembly 和 InteractiveAuto
- 将 CascadingAuthenticationState 迁移到服务 — 将组件包装器替换为服务
- MapStaticAssets — .NET 9+ 中的优化静态文件服务
- 从 ASP.NET Core 7.0 迁移到 8.0 — ASP.NET Core 所有变更的常规迁移指南
- 使用 Blazor 进行流式呈现 — 使用
@attribute [StreamRendering]异步加载数据 - 级联值与呈现模式边界 — 级联参数为何无法跨越呈现模式边界