convert-blazor-server-to-webapp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convert Blazor Server App to Blazor Web App

将Blazor Server应用转换为Blazor Web App

This skill helps an agent convert a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. The old hosting model uses
AddServerSideBlazor
/
MapBlazorHub
with a
_Host.cshtml
Razor Page as the entry point. The new Blazor Web App model uses
AddRazorComponents
/
MapRazorComponents
with an
App.razor
root component, enabling per-component render modes, enhanced navigation, streaming rendering, and other .NET 8+ features. The converted app uses
InteractiveServer
render mode to preserve existing interactive behavior.
本技能可帮助Agent将.NET 8之前的Blazor Server应用转换为.NET 8及以上版本的Blazor Web App。旧托管模型使用
AddServerSideBlazor
/
MapBlazorHub
,并以
_Host.cshtml
Razor页面作为入口点。新的Blazor Web App模型使用
AddRazorComponents
/
MapRazorComponents
,并以
App.razor
作为根组件,支持按组件设置渲染模式、增强导航、流式渲染等.NET 8+特性。转换后的应用使用
InteractiveServer
渲染模式,以保留原有的交互行为。

When to Use

适用场景

  • Migrating a Blazor Server app from .NET 6 or .NET 7 to .NET 8+
  • App currently uses
    AddServerSideBlazor()
    and
    MapBlazorHub()
    in
    Program.cs
    (or
    Startup.cs
    )
  • App uses
    Pages/_Host.cshtml
    (or
    _Host.razor
    ) as the host page with Component Tag Helpers
  • Want to adopt new Blazor Web App features while keeping interactive server rendering
  • 将.NET 6或.NET 7的Blazor Server应用迁移至.NET 8及以上版本
  • 当前应用在
    Program.cs
    (或
    Startup.cs
    )中使用
    AddServerSideBlazor()
    MapBlazorHub()
  • 应用使用
    Pages/_Host.cshtml
    (或
    _Host.razor
    )作为宿主页面,并使用组件标签助手
  • 希望在保留交互式服务器渲染的同时,采用Blazor Web App的新特性

When Not to Use

不适用场景

  • The app already uses
    AddRazorComponents
    and
    MapRazorComponents
    .
    It is already a Blazor Web App — no conversion is needed. Stop here and tell the user the app is already using the Blazor Web App model.
  • Blazor WebAssembly or hosted Blazor WebAssembly app — these have a different migration path
  • The app should stay on the legacy Blazor Server hosting model (just update TFM and packages)
  • The app targets .NET Framework — it must be migrated to .NET first
  • 应用已使用
    AddRazorComponents
    MapRazorComponents
    :该应用已是Blazor Web App,无需转换。请告知用户应用已采用Blazor Web App模型,停止操作。
  • Blazor WebAssembly或托管式Blazor WebAssembly应用:这类应用的迁移路径不同
  • 需保留旧版Blazor Server托管模型的应用(仅更新目标框架名称(TFM)和包即可)
  • 以.NET Framework为目标框架的应用:需先迁移至.NET平台

Inputs

输入项

InputRequiredDescription
Blazor Server projectYesThe
.csproj
and source files of the Blazor Server app
Target frameworkYes.NET 8 or later (e.g.,
net8.0
,
net9.0
,
net10.0
)
Program.cs
or
Startup.cs
YesThe app's service and middleware configuration
_Host.cshtml
location
RecommendedUsually
Pages/_Host.cshtml
; may be
_Host.razor
in some projects
输入项是否必填描述
Blazor Server项目Blazor Server应用的
.csproj
文件及源码文件
目标框架.NET 8或更高版本(例如:
net8.0
net9.0
net10.0
Program.cs
Startup.cs
应用的服务和中间件配置文件
_Host.cshtml
位置
推荐通常为
Pages/_Host.cshtml
;部分项目中可能为
_Host.razor

Workflow

工作流程

Commit strategy: Commit after each logical step so the migration is reviewable and bisectable.
提交策略:完成每个逻辑步骤后提交,使迁移过程可审查、可二分排查。

Step 1: Update the project file

步骤1:更新项目文件

Update the
.csproj
file:
  1. Change the Target Framework Moniker (TFM) to the target version:
    xml
    <TargetFramework>net8.0</TargetFramework>
  2. Update all
    Microsoft.AspNetCore.*
    ,
    Microsoft.EntityFrameworkCore.*
    ,
    Microsoft.Extensions.*
    , and
    System.Net.Http.Json
    package references to the matching version.
For non-Blazor project file changes (nullable reference types, implicit usings, HTTP/3 support, etc.), see the general ASP.NET Core migration guide.
更新
.csproj
文件:
  1. 将目标框架名称(TFM)修改为目标版本:
    xml
    <TargetFramework>net8.0</TargetFramework>
  2. 将所有
    Microsoft.AspNetCore.*
    Microsoft.EntityFrameworkCore.*
    Microsoft.Extensions.*
    System.Net.Http.Json
    包引用更新为匹配版本。
关于非Blazor项目文件的修改(可为空引用类型、隐式using、HTTP/3支持等),请参阅通用ASP.NET Core迁移指南

Step 2: Create
Routes.razor
from
App.razor

步骤2:从
App.razor
创建
Routes.razor

The old
App.razor
contains the
<Router>
component. This content moves to a new
Routes.razor
file so that
App.razor
can become the root HTML document component.
  1. Create a new file
    Routes.razor
    in the project root.
  2. Move the entire content of
    App.razor
    into
    Routes.razor
    .
  3. If the content is wrapped in
    <CascadingAuthenticationState>
    , remove that wrapper (it will be replaced by a service in Step 5).
  4. Leave
    App.razor
    empty for the next step.
The resulting
Routes.razor
should look similar to:
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>
If the app uses
<AuthorizeRouteView>
instead of
<RouteView>
, keep it — it works the same way in Blazor Web Apps.
旧版
App.razor
包含
<Router>
组件。需将这部分内容移至新的
Routes.razor
文件,以便
App.razor
可作为根HTML文档组件。
  1. 在项目根目录创建新文件
    Routes.razor
  2. App.razor
    的全部内容移至
    Routes.razor
  3. 如果内容被
    <CascadingAuthenticationState>
    包裹,请移除该包裹(将在步骤5中替换为服务)。
  4. 保留
    App.razor
    为空,以便进行下一步操作。
最终的
Routes.razor
应类似如下:
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>抱歉,该地址无内容。</p>
        </LayoutView>
    </NotFound>
</Router>
如果应用使用
<AuthorizeRouteView>
而非
<RouteView>
,请保留该组件——它在Blazor Web App中的工作方式相同。

Step 3: Convert
_Host.cshtml
to
App.razor

步骤3:将
_Host.cshtml
转换为
App.razor

Move the HTML shell from
Pages/_Host.cshtml
into the now-empty
App.razor
and transform it from a Razor Page into a Razor component:
  1. Remove Razor Page directives — delete
    @page "/"
    ,
    @using Microsoft.AspNetCore.Components.Web
    ,
    @namespace
    , and
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    .
  2. Add component injection — if using environment-conditional error UI, add:
    razor
    @inject IHostEnvironment Env
  3. Fix the base tag — replace
    <base href="~/" />
    with
    <base href="/" />
    .
  4. Replace HeadOutlet Component Tag Helper — replace:
    html
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
    with:
    razor
    <HeadOutlet @rendermode="InteractiveServer" />
  5. Replace App Component Tag Helper with Routes — replace:
    html
    <component type="typeof(App)" render-mode="ServerPrerendered" />
    with:
    razor
    <Routes @rendermode="InteractiveServer" />
  6. Replace Environment Tag Helpers — replace:
    html
    <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>
    with:
    razor
    @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. Update the Blazor script — replace:
    html
    <script src="_framework/blazor.server.js"></script>
    with:
    html
    <script src="_framework/blazor.web.js"></script>
  8. Add render mode import — add to
    _Imports.razor
    :
    razor
    @using static Microsoft.AspNetCore.Components.Web.RenderMode
  9. Delete
    Pages/_Host.cshtml
    (and
    Pages/_Host.cshtml.cs
    if it exists).
Prerendering note: If the original app used
render-mode="Server"
(not
"ServerPrerendered"
), prerendering was disabled. Preserve this by using
new InteractiveServerRenderMode(prerender: false)
instead of
InteractiveServer
for both
HeadOutlet
and
Routes
.
Pages/_Host.cshtml
中的HTML外壳移至现在为空的
App.razor
,并将其从Razor页面转换为Razor组件:
  1. 移除Razor页面指令——删除
    @page "/"
    @using Microsoft.AspNetCore.Components.Web
    @namespace
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  2. 添加组件注入——如果使用环境条件错误UI,请添加:
    razor
    @inject IHostEnvironment Env
  3. 修复base标签——将
    <base href="~/" />
    替换为
    <base href="/" />
  4. 替换HeadOutlet组件标签助手——将:
    html
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
    替换为:
    razor
    <HeadOutlet @rendermode="InteractiveServer" />
  5. 将应用组件标签助手替换为Routes——将:
    html
    <component type="typeof(App)" render-mode="ServerPrerendered" />
    替换为:
    razor
    <Routes @rendermode="InteractiveServer" />
  6. 替换环境标签助手——将:
    html
    <environment include="Staging,Production">
        发生错误。此应用可能需重新加载才能恢复响应。
    </environment>
    <environment include="Development">
        发生未处理的异常。请查看浏览器开发者工具了解详情。
    </environment>
    替换为:
    razor
    @if (Env.IsDevelopment())
    {
        <text>
            发生未处理的异常。请查看浏览器开发者工具了解详情。
        </text>
    }
    else
    {
        <text>
            发生错误。此应用可能需重新加载才能恢复响应。
        </text>
    }
  7. 更新Blazor脚本——将:
    html
    <script src="_framework/blazor.server.js"></script>
    替换为:
    html
    <script src="_framework/blazor.web.js"></script>
  8. 添加渲染模式导入——在
    _Imports.razor
    中添加:
    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
配置该模式,以保留原有行为。

Step 4: Update
Program.cs

步骤4:更新
Program.cs

Make the following changes to
Program.cs
(or
Startup.cs
if the app uses the older hosting pattern):
  1. Replace Blazor Server services — replace:
    csharp
    builder.Services.AddServerSideBlazor();
    with:
    csharp
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents();
    If
    AddServerSideBlazor
    had options configured (e.g., circuit options, hub options, detailed errors), migrate them to
    AddInteractiveServerComponents
    :
    csharp
    // 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. Replace Blazor endpoint mapping — replace:
    csharp
    app.MapBlazorHub();
    with:
    csharp
    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode();
    Ensure there is a
    using
    statement for the project's root namespace so that
    App
    resolves to the
    App.razor
    component.
  3. Remove the fallback route — delete:
    csharp
    app.MapFallbackToPage("/_Host");
  4. Remove explicit routing middleware — delete if present:
    csharp
    app.UseRouting();
    Endpoint routing is the default and explicit
    UseRouting()
    is no longer needed.
  5. Add antiforgery middleware — add after
    UseAuthentication
    /
    UseAuthorization
    if present:
    csharp
    app.UseAntiforgery();
    AddRazorComponents
    registers antiforgery services automatically, but the middleware must be explicitly added to the pipeline. Without it, form POST requests fail with 400 errors.
Program.cs
(如果应用使用旧托管模式则为
Startup.cs
)进行以下修改:
  1. 替换Blazor Server服务——将:
    csharp
    builder.Services.AddServerSideBlazor();
    替换为:
    csharp
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents();
    如果
    AddServerSideBlazor
    配置了选项(例如:电路选项、集线器选项、详细错误信息),请将这些选项迁移至
    AddInteractiveServerComponents
    csharp
    // 旧代码:
    builder.Services.AddServerSideBlazor(options =>
    {
        options.DetailedErrors = true;
        options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
    });
    
    // 新代码:
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents(options =>
        {
            options.DetailedErrors = true;
            options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
        });
  2. 替换Blazor端点映射——将:
    csharp
    app.MapBlazorHub();
    替换为:
    csharp
    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode();
    请确保存在项目根命名空间的
    using
    语句,以便
    App
    可解析为
    App.razor
    组件。
  3. 移除回退路由——删除:
    csharp
    app.MapFallbackToPage("/_Host");
  4. 移除显式路由中间件——如果存在则删除:
    csharp
    app.UseRouting();
    端点路由是默认配置,显式的
    UseRouting()
    不再需要。
  5. 添加防伪中间件——如果存在
    UseAuthentication
    /
    UseAuthorization
    ,则在其后添加:
    csharp
    app.UseAntiforgery();
    AddRazorComponents
    会自动注册防伪服务,但必须显式将中间件添加到管道中。否则,表单POST请求将因400错误而失败。

Step 5: Migrate
CascadingAuthenticationState
(if present)

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

If the app used
<CascadingAuthenticationState>
to wrap the router:
  1. Remove the
    <CascadingAuthenticationState>
    component wrapper (already done in Step 2 if following this workflow).
  2. Add the cascading authentication state service in
    Program.cs
    :
    csharp
    builder.Services.AddCascadingAuthenticationState();
The component wrapper approach does not work across render mode boundaries in Blazor Web Apps. The service-based approach provides
Task<AuthenticationState>
as a cascading value to all components regardless of render mode.
如果应用使用
<CascadingAuthenticationState>
包裹路由器:
  1. 移除
    <CascadingAuthenticationState>
    组件包裹(如果遵循本工作流程,步骤2中已完成此操作)。
  2. Program.cs
    中添加级联认证状态服务:
    csharp
    builder.Services.AddCascadingAuthenticationState();
在Blazor Web App中,组件包裹方式无法跨渲染模式边界工作。基于服务的方式会将
Task<AuthenticationState>
作为级联值提供给所有组件,无论其渲染模式如何。

Step 6: Recommended improvements (optional)

步骤6:推荐的改进(可选)

These are optional modernization improvements — not required for the conversion to work. If you suggest any of these, state explicitly that they are optional.
  • Replace
    UseStaticFiles
    with
    MapStaticAssets
    (.NET 9+):
    app.MapStaticAssets()
    provides optimized static file serving with fingerprinting, pre-compression, and content-based ETags. See MapStaticAssets documentation.
  • Add
    @attribute [StreamRendering]
    to pages with async data loading (
    OnInitializedAsync
    ) for improved perceived performance. The page renders its initial synchronous content immediately and re-renders when async data arrives.
  • Update CSS isolation bundle reference if the
    <link>
    tag referenced a
    _Host
    assembly name; ensure it matches the project's actual assembly name:
    <link href="{AssemblyName}.styles.css" rel="stylesheet" />
    .
  • For other non-Blazor improvements (minimal hosting, HTTP/3, output caching, etc.), see the general ASP.NET Core migration guide.
这些是可选的现代化改进——转换工作无需依赖它们。如果建议使用其中任何一项,请明确说明它们是可选的。
  • MapStaticAssets
    替换
    UseStaticFiles
    (.NET 9+):
    app.MapStaticAssets()
    提供优化的静态文件服务,支持指纹识别、预压缩和基于内容的ETag。请参阅MapStaticAssets文档
  • **添加
    @attribute [StreamRendering]
    **到包含异步数据加载(
    OnInitializedAsync
    )的页面,以提升感知性能。页面会立即渲染初始同步内容,并在异步数据到达时重新渲染。
  • 更新CSS隔离包引用:如果
    <link>
    标签引用了
    _Host
    程序集名称,请确保其与项目实际程序集名称匹配:
    <link href="{AssemblyName}.styles.css" rel="stylesheet" />
  • 其他非Blazor改进(最小托管、HTTP/3、输出缓存等)请参阅通用ASP.NET Core迁移指南

Step 7: Verify the migration

步骤7:验证迁移

  1. Build the project targeting the new framework. Confirm no compile errors.
  2. Search for remaining references to removed APIs:
    • AddServerSideBlazor
    • MapBlazorHub
    • MapFallbackToPage
    • blazor.server.js
    • _Host.cshtml
  3. Run the app and verify:
    • Pages load and render correctly
    • Interactive features work (forms, event handlers, SignalR circuits)
    • Navigation between pages works
    • Authentication and authorization flows work if present
  4. Run existing tests.
  1. 以新框架为目标构建项目,确认无编译错误。
  2. 搜索是否存在已移除API的剩余引用:
    • AddServerSideBlazor
    • MapBlazorHub
    • MapFallbackToPage
    • blazor.server.js
    • _Host.cshtml
  3. 运行应用并验证:
    • 页面加载和渲染正常
    • 交互功能正常(表单、事件处理程序、SignalR电路)
    • 页面间导航正常
    • 认证和授权流程(如果存在)正常
  4. 运行现有测试。

Validation

验证清单

  • No references to
    AddServerSideBlazor
    remain
  • No references to
    MapBlazorHub
    remain
  • No references to
    MapFallbackToPage("/_Host")
    remain
  • No references to
    blazor.server.js
    remain
  • Pages/_Host.cshtml
    has been deleted
  • App.razor
    serves as the root component with a full HTML document structure
  • Routes.razor
    contains the
    <Router>
    configuration
  • Program.cs
    uses
    AddRazorComponents().AddInteractiveServerComponents()
  • Program.cs
    uses
    MapRazorComponents<App>().AddInteractiveServerRenderMode()
  • app.UseAntiforgery()
    is present in the middleware pipeline
  • If the app used
    <CascadingAuthenticationState>
    , it has been replaced with
    AddCascadingAuthenticationState()
    service registration
  • App builds and runs successfully on the target framework
  • 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()
    服务注册
  • 应用可在目标框架上成功构建并运行

Common Pitfalls

常见陷阱

PitfallSolution
Missing
UseAntiforgery()
middleware
AddRazorComponents
registers antiforgery services, but the middleware must be explicitly added. Place
app.UseAntiforgery()
after
UseAuthentication
/
UseAuthorization
. Without it, form POST requests fail with 400 errors.
Forgetting to replace
blazor.server.js
with
blazor.web.js
The old script does not work with the Blazor Web App model. Replace all references to
_framework/blazor.server.js
with
_framework/blazor.web.js
.
Not removing
<CascadingAuthenticationState>
wrapper
The component wrapper does not work across render mode boundaries in Blazor Web Apps. Use
builder.Services.AddCascadingAuthenticationState()
instead.
Leaving
app.UseRouting()
in the pipeline
Explicit
UseRouting()
is no longer needed and can interfere with endpoint routing. Remove it unless other middleware specifically requires it.
Using
InteractiveServer
when prerendering was disabled
If the original app used
render-mode="Server"
(not
"ServerPrerendered"
), use
new InteractiveServerRenderMode(prerender: false)
to preserve the same behavior. Using
InteractiveServer
enables prerendering which can cause unexpected issues with components that depend on JS interop during initialization.
Not migrating
AddServerSideBlazor
circuit options
If circuit options, hub options, or detailed error settings were configured, migrate them to
AddInteractiveServerComponents(options => { ... })
. Otherwise those settings are silently lost.
UseAntiforgery()
placed before authentication middleware
The antiforgery middleware must be placed after
UseAuthentication
and
UseAuthorization
. Placing it before causes antiforgery validation to run before the user identity is established.
CSS isolation bundle link has wrong assembly nameIf the
<link href="{Name}.styles.css">
tag referenced the old project name, update it to match the current assembly name.
陷阱解决方案
缺少
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 App中,组件包裹方式无法跨渲染模式边界工作。请使用
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">
标签引用了旧项目名称,请更新为当前程序集名称。

More Info

更多信息