mcpField
C
convert-blazor-server-to-webapp

convert-blazor-server-to-webapp

github.com/dotnet开发5.8k3.1k 次安装1 天前更新
代码仓库

关于 convert-blazor-server-to-webapp

该技能指导 Agent 将 .NET 6/7 的 Blazor Server 应用迁移到 .NET 8+ Blazor Web App,同时保留 InteractiveServer 交互模式。内容覆盖组件重组、服务与端点更新,以及迁移验证和常见陷阱。

主要功能

  1. 01更新目标框架与相关包
  2. 02将路由内容移至 Routes.razor
  3. 03把 _Host.cshtml 转为 App.razor
  4. 04替换 Blazor 服务与端点映射
  5. 05迁移身份验证状态并验证应用

适用场景

  1. 01将旧版 Blazor Server 升级到 .NET 8+
  2. 02保留交互式服务器渲染并采用新应用模型
  3. 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 文件:

  1. 将目标框架名字 (TFM) 更改为目标版本:
    <TargetFramework>net8.0</TargetFramework>
    
  2. 将所有 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 文档组件。

  1. 在项目根目录中创建新文件 Routes.razor。
  2. 将 App.razor 的全部内容移至 Routes.razor。
  3. 如果内容被 <CascadingAuthenticationState> 包裹,请移除该包装器(它将在步骤 5 中由服务替代)。
  4. 保持 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 组件:

  1. 移除 Razor 页面指令 — 删除 @page "/"、@using Microsoft.AspNetCore.Components.Web、@namespace 和 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers。

  2. 添加组件注入 — 如果使用依赖环境的错误界面,请添加:

    @inject IHostEnvironment Env
    
  3. 修正 base 标记 — 将 <base href="~/" /> 替换为 <base href="/" />。

  4. 替换 HeadOutlet 组件标记帮助器 — 将:

    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
    

    替换为:

    <HeadOutlet @rendermode="InteractiveServer" />
    
  5. 将 App 组件标记帮助器替换为 Routes — 将:

    <component type="typeof(App)" render-mode="ServerPrerendered" />
    

    替换为:

    <Routes @rendermode="InteractiveServer" />
    
  6. 替换 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>
    }
    
  7. 更新 Blazor 脚本 — 将:

    <script src="_framework/blazor.server.js"></script>
    

    替换为:

    <script src="_framework/blazor.web.js"></script>
    
  8. 添加渲染模式导入 — 添加到 _Imports.razor:

    @using static Microsoft.AspNetCore.Components.Web.RenderMode
    
  9. 删除 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)进行以下更改:

  1. 替换 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);
        });
    
  2. 替换 Blazor 终结点映射 — 将:

    app.MapBlazorHub();
    

    替换为:

    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode();
    

    确保有一条针对项目根命名空间的 using 语句,以便 App 能解析到 App.razor 组件。

  3. 移除回退路由 — 删除:

    app.MapFallbackToPage("/_Host");
    
  4. 移除显式路由中间件 — 如果存在,请删除:

    app.UseRouting();
    

    终结点路由是默认设置,不再需要显式调用 UseRouting()。

  5. 添加防伪中间件 — 如果存在 UseAuthentication/UseAuthorization,请在其后添加:

    app.UseAntiforgery();
    

    AddRazorComponents 会自动注册防伪服务,但必须在管道中显式添加中间件。否则,表单 POST 请求会因 400 错误而失败。

步骤 5:迁移 CascadingAuthenticationState(如果存在)

如果应用使用 <CascadingAuthenticationState> 包裹路由器:

  1. 移除 <CascadingAuthenticationState> 组件包装器(如果按此工作流操作,已在步骤 2 中完成)。
  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 步:验证迁移
  1. 以新框架为目标构建项目。确认没有编译错误。
  2. 搜索是否仍有对已移除 API 的引用:
    • AddServerSideBlazor
    • MapBlazorHub
    • MapFallbackToPage
    • blazor.server.js
    • _Host.cshtml
  3. 运行应用并验证:
    • 页面能正常加载和呈现
    • 交互功能正常(表单、事件处理程序、SignalR 电路)
    • 页面间导航正常
    • 如有身份验证和授权流程,则确保其正常运行
  4. 运行现有测试。

验证清单

  • 不再引用 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"> 标签引用的是旧项目名称,请将其更新为当前程序集名称。

更多信息

评论

支持 Markdown