convert-blazor-server-to-webapp
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvert 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 / with a Razor Page as the entry point. The new Blazor Web App model uses / with an root component, enabling per-component render modes, enhanced navigation, streaming rendering, and other .NET 8+ features. The converted app uses render mode to preserve existing interactive behavior.
AddServerSideBlazorMapBlazorHub_Host.cshtmlAddRazorComponentsMapRazorComponentsApp.razorInteractiveServer本技能可帮助Agent将.NET 8之前的Blazor Server应用转换为.NET 8及以上版本的Blazor Web App。旧托管模型使用/,并以 Razor页面作为入口点。新的Blazor Web App模型使用/,并以作为根组件,支持按组件设置渲染模式、增强导航、流式渲染等.NET 8+特性。转换后的应用使用渲染模式,以保留原有的交互行为。
AddServerSideBlazorMapBlazorHub_Host.cshtmlAddRazorComponentsMapRazorComponentsApp.razorInteractiveServerWhen to Use
适用场景
- Migrating a Blazor Server app from .NET 6 or .NET 7 to .NET 8+
- App currently uses and
AddServerSideBlazor()inMapBlazorHub()(orProgram.cs)Startup.cs - App uses (or
Pages/_Host.cshtml) as the host page with Component Tag Helpers_Host.razor - 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 and
AddRazorComponents. 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.MapRazorComponents - 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:该应用已是Blazor Web App,无需转换。请告知用户应用已采用Blazor Web App模型,停止操作。MapRazorComponents - Blazor WebAssembly或托管式Blazor WebAssembly应用:这类应用的迁移路径不同
- 需保留旧版Blazor Server托管模型的应用(仅更新目标框架名称(TFM)和包即可)
- 以.NET Framework为目标框架的应用:需先迁移至.NET平台
Inputs
输入项
| Input | Required | Description |
|---|---|---|
| Blazor Server project | Yes | The |
| Target framework | Yes | .NET 8 or later (e.g., |
| Yes | The app's service and middleware configuration |
| Recommended | Usually |
| 输入项 | 是否必填 | 描述 |
|---|---|---|
| Blazor Server项目 | 是 | Blazor Server应用的 |
| 目标框架 | 是 | .NET 8或更高版本(例如: |
| 是 | 应用的服务和中间件配置文件 |
| 推荐 | 通常为 |
Workflow
工作流程
Commit strategy: Commit after each logical step so the migration is reviewable and bisectable.
提交策略:完成每个逻辑步骤后提交,使迁移过程可审查、可二分排查。
Step 1: Update the project file
步骤1:更新项目文件
Update the file:
.csproj- Change the Target Framework Moniker (TFM) to the target version:
xml
<TargetFramework>net8.0</TargetFramework> - Update all ,
Microsoft.AspNetCore.*,Microsoft.EntityFrameworkCore.*, andMicrosoft.Extensions.*package references to the matching version.System.Net.Http.Json
For non-Blazor project file changes (nullable reference types, implicit usings, HTTP/3 support, etc.), see the general ASP.NET Core migration guide.
更新文件:
.csproj- 将目标框架名称(TFM)修改为目标版本:
xml
<TargetFramework>net8.0</TargetFramework> - 将所有、
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
Routes.razorApp.razor步骤2:从App.razor
创建Routes.razor
App.razorRoutes.razorThe old contains the component. This content moves to a new file so that can become the root HTML document component.
App.razor<Router>Routes.razorApp.razor- Create a new file in the project root.
Routes.razor - Move the entire content of into
App.razor.Routes.razor - If the content is wrapped in , remove that wrapper (it will be replaced by a service in Step 5).
<CascadingAuthenticationState> - Leave empty for the next step.
App.razor
The resulting should look similar to:
Routes.razorrazor
<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 instead of , keep it — it works the same way in Blazor Web Apps.
<AuthorizeRouteView><RouteView>旧版包含组件。需将这部分内容移至新的文件,以便可作为根HTML文档组件。
App.razor<Router>Routes.razorApp.razor- 在项目根目录创建新文件。
Routes.razor - 将的全部内容移至
App.razor。Routes.razor - 如果内容被包裹,请移除该包裹(将在步骤5中替换为服务)。
<CascadingAuthenticationState> - 保留为空,以便进行下一步操作。
App.razor
最终的应类似如下:
Routes.razorrazor
<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>如果应用使用而非,请保留该组件——它在Blazor Web App中的工作方式相同。
<AuthorizeRouteView><RouteView>Step 3: Convert _Host.cshtml
to App.razor
_Host.cshtmlApp.razor步骤3:将_Host.cshtml
转换为App.razor
_Host.cshtmlApp.razorMove the HTML shell from into the now-empty and transform it from a Razor Page into a Razor component:
Pages/_Host.cshtmlApp.razor-
Remove Razor Page directives — delete,
@page "/",@using Microsoft.AspNetCore.Components.Web, and@namespace.@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -
Add component injection — if using environment-conditional error UI, add:razor
@inject IHostEnvironment Env -
Fix the base tag — replacewith
<base href="~/" />.<base href="/" /> -
Replace HeadOutlet Component Tag Helper — replace:html
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />with:razor<HeadOutlet @rendermode="InteractiveServer" /> -
Replace App Component Tag Helper with Routes — replace:html
<component type="typeof(App)" render-mode="ServerPrerendered" />with:razor<Routes @rendermode="InteractiveServer" /> -
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> } -
Update the Blazor script — replace:html
<script src="_framework/blazor.server.js"></script>with:html<script src="_framework/blazor.web.js"></script> -
Add render mode import — add to:
_Imports.razorrazor@using static Microsoft.AspNetCore.Components.Web.RenderMode -
Delete(and
Pages/_Host.cshtmlif it exists).Pages/_Host.cshtml.cs
Prerendering note: If the original app used (not ), prerendering was disabled. Preserve this by using instead of for both and .
render-mode="Server""ServerPrerendered"new InteractiveServerRenderMode(prerender: false)InteractiveServerHeadOutletRoutes将中的HTML外壳移至现在为空的,并将其从Razor页面转换为Razor组件:
Pages/_Host.cshtmlApp.razor-
移除Razor页面指令——删除、
@page "/"、@using Microsoft.AspNetCore.Components.Web和@namespace。@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -
添加组件注入——如果使用环境条件错误UI,请添加:razor
@inject IHostEnvironment Env -
修复base标签——将替换为
<base href="~/" />。<base href="/" /> -
替换HeadOutlet组件标签助手——将:html
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />替换为:razor<HeadOutlet @rendermode="InteractiveServer" /> -
将应用组件标签助手替换为Routes——将:html
<component type="typeof(App)" render-mode="ServerPrerendered" />替换为:razor<Routes @rendermode="InteractiveServer" /> -
替换环境标签助手——将:html
<environment include="Staging,Production"> 发生错误。此应用可能需重新加载才能恢复响应。 </environment> <environment include="Development"> 发生未处理的异常。请查看浏览器开发者工具了解详情。 </environment>替换为:razor@if (Env.IsDevelopment()) { <text> 发生未处理的异常。请查看浏览器开发者工具了解详情。 </text> } else { <text> 发生错误。此应用可能需重新加载才能恢复响应。 </text> } -
更新Blazor脚本——将:html
<script src="_framework/blazor.server.js"></script>替换为:html<script src="_framework/blazor.web.js"></script> -
添加渲染模式导入——在中添加:
_Imports.razorrazor@using static Microsoft.AspNetCore.Components.Web.RenderMode -
删除(如果存在
Pages/_Host.cshtml也一并删除)。Pages/_Host.cshtml.cs
预渲染说明:如果原应用使用(而非),则预渲染已被禁用。请使用替代,为和配置该模式,以保留原有行为。
render-mode="Server""ServerPrerendered"new InteractiveServerRenderMode(prerender: false)InteractiveServerHeadOutletRoutesStep 4: Update Program.cs
Program.cs步骤4:更新Program.cs
Program.csMake the following changes to (or if the app uses the older hosting pattern):
Program.csStartup.cs-
Replace Blazor Server services — replace:csharp
builder.Services.AddServerSideBlazor();with:csharpbuilder.Services.AddRazorComponents() .AddInteractiveServerComponents();Ifhad options configured (e.g., circuit options, hub options, detailed errors), migrate them toAddServerSideBlazor:AddInteractiveServerComponentscsharp// 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); }); -
Replace Blazor endpoint mapping — replace:csharp
app.MapBlazorHub();with:csharpapp.MapRazorComponents<App>() .AddInteractiveServerRenderMode();Ensure there is astatement for the project's root namespace so thatusingresolves to theAppcomponent.App.razor -
Remove the fallback route — delete:csharp
app.MapFallbackToPage("/_Host"); -
Remove explicit routing middleware — delete if present:csharp
app.UseRouting();Endpoint routing is the default and explicitis no longer needed.UseRouting() -
Add antiforgery middleware — add after/
UseAuthenticationif present:UseAuthorizationcsharpapp.UseAntiforgery();registers antiforgery services automatically, but the middleware must be explicitly added to the pipeline. Without it, form POST requests fail with 400 errors.AddRazorComponents
对(如果应用使用旧托管模式则为)进行以下修改:
Program.csStartup.cs-
替换Blazor Server服务——将:csharp
builder.Services.AddServerSideBlazor();替换为:csharpbuilder.Services.AddRazorComponents() .AddInteractiveServerComponents();如果配置了选项(例如:电路选项、集线器选项、详细错误信息),请将这些选项迁移至AddServerSideBlazor:AddInteractiveServerComponentscsharp// 旧代码: 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); }); -
替换Blazor端点映射——将:csharp
app.MapBlazorHub();替换为:csharpapp.MapRazorComponents<App>() .AddInteractiveServerRenderMode();请确保存在项目根命名空间的语句,以便using可解析为App组件。App.razor -
移除回退路由——删除:csharp
app.MapFallbackToPage("/_Host"); -
移除显式路由中间件——如果存在则删除:csharp
app.UseRouting();端点路由是默认配置,显式的不再需要。UseRouting() -
添加防伪中间件——如果存在/
UseAuthentication,则在其后添加:UseAuthorizationcsharpapp.UseAntiforgery();会自动注册防伪服务,但必须显式将中间件添加到管道中。否则,表单POST请求将因400错误而失败。AddRazorComponents
Step 5: Migrate CascadingAuthenticationState
(if present)
CascadingAuthenticationState步骤5:迁移CascadingAuthenticationState
(如果存在)
CascadingAuthenticationStateIf the app used to wrap the router:
<CascadingAuthenticationState>- Remove the component wrapper (already done in Step 2 if following this workflow).
<CascadingAuthenticationState> - Add the cascading authentication state service in :
Program.cscsharpbuilder.Services.AddCascadingAuthenticationState();
The component wrapper approach does not work across render mode boundaries in Blazor Web Apps. The service-based approach provides as a cascading value to all components regardless of render mode.
Task<AuthenticationState>如果应用使用包裹路由器:
<CascadingAuthenticationState>- 移除组件包裹(如果遵循本工作流程,步骤2中已完成此操作)。
<CascadingAuthenticationState> - 在中添加级联认证状态服务:
Program.cscsharpbuilder.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 with
UseStaticFiles(.NET 9+):MapStaticAssetsprovides optimized static file serving with fingerprinting, pre-compression, and content-based ETags. See MapStaticAssets documentation.app.MapStaticAssets() - Add to pages with async data loading (
@attribute [StreamRendering]) for improved perceived performance. The page renders its initial synchronous content immediately and re-renders when async data arrives.OnInitializedAsync - Update CSS isolation bundle reference if the tag referenced a
<link>assembly name; ensure it matches the project's actual assembly name:_Host.<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(.NET 9+):UseStaticFiles提供优化的静态文件服务,支持指纹识别、预压缩和基于内容的ETag。请参阅MapStaticAssets文档。app.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:验证迁移
- Build the project targeting the new framework. Confirm no compile errors.
- Search for remaining references to removed APIs:
AddServerSideBlazorMapBlazorHubMapFallbackToPageblazor.server.js_Host.cshtml
- 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
- Run existing tests.
- 以新框架为目标构建项目,确认无编译错误。
- 搜索是否存在已移除API的剩余引用:
AddServerSideBlazorMapBlazorHubMapFallbackToPageblazor.server.js_Host.cshtml
- 运行应用并验证:
- 页面加载和渲染正常
- 交互功能正常(表单、事件处理程序、SignalR电路)
- 页面间导航正常
- 认证和授权流程(如果存在)正常
- 运行现有测试。
Validation
验证清单
- No references to remain
AddServerSideBlazor - No references to remain
MapBlazorHub - No references to remain
MapFallbackToPage("/_Host") - No references to remain
blazor.server.js - has been deleted
Pages/_Host.cshtml - serves as the root component with a full HTML document structure
App.razor - contains the
Routes.razorconfiguration<Router> - uses
Program.csAddRazorComponents().AddInteractiveServerComponents() - uses
Program.csMapRazorComponents<App>().AddInteractiveServerRenderMode() - is present in the middleware pipeline
app.UseAntiforgery() - If the app used , it has been replaced with
<CascadingAuthenticationState>service registrationAddCascadingAuthenticationState() - App builds and runs successfully on the target framework
- 无的剩余引用
AddServerSideBlazor - 无的剩余引用
MapBlazorHub - 无的剩余引用
MapFallbackToPage("/_Host") - 无的剩余引用
blazor.server.js - 已删除
Pages/_Host.cshtml - 作为根组件,包含完整的HTML文档结构
App.razor - 包含
Routes.razor配置<Router> - 使用
Program.csAddRazorComponents().AddInteractiveServerComponents() - 使用
Program.csMapRazorComponents<App>().AddInteractiveServerRenderMode() - 中间件管道中存在
app.UseAntiforgery() - 如果应用曾使用,已替换为
<CascadingAuthenticationState>服务注册AddCascadingAuthenticationState() - 应用可在目标框架上成功构建并运行
Common Pitfalls
常见陷阱
| Pitfall | Solution |
|---|---|
Missing | |
Forgetting to replace | The old script does not work with the Blazor Web App model. Replace all references to |
Not removing | The component wrapper does not work across render mode boundaries in Blazor Web Apps. Use |
Leaving | Explicit |
Using | If the original app used |
Not migrating | If circuit options, hub options, or detailed error settings were configured, migrate them to |
| The antiforgery middleware must be placed after |
| CSS isolation bundle link has wrong assembly name | If the |
| 陷阱 | 解决方案 |
|---|---|
缺少 | |
忘记将 | 旧脚本无法在Blazor Web App模型中工作。将所有 |
未移除 | 在Blazor Web App中,组件包裹方式无法跨渲染模式边界工作。请使用 |
管道中保留 | 显式的 |
当初始化禁用预渲染时使用 | 如果原应用使用 |
未迁移 | 如果配置了电路选项、集线器选项或详细错误设置,请将它们迁移至 |
| 防伪中间件必须置于 |
| CSS隔离包链接的程序集名称错误 | 如果 |
More Info
更多信息
- Convert a Blazor Server app into a Blazor Web App — the official step-by-step migration guide
- ASP.NET Core Blazor render modes — understanding InteractiveServer, InteractiveWebAssembly, and InteractiveAuto
- Migrate CascadingAuthenticationState to services — replacing the component wrapper with a service
- MapStaticAssets — optimized static file serving in .NET 9+
- Migrate from ASP.NET Core 7.0 to 8.0 — general migration guide for all ASP.NET Core changes
- Stream rendering with Blazor — for async data loading
@attribute [StreamRendering] - Cascading values and render mode boundaries — why cascading parameters do not cross render mode boundaries
- 将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] - 级联值与渲染模式边界——级联参数无法跨渲染模式边界的原因