协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
convert-blazor-server-to-webapp【开发与工程】Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor root component, replacing blazor.server.js with blazor.web.js, migrating CascadingAuthenticationState to a …
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
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.
AddServerSideBlazor() and MapBlazorHub() in Program.cs (or Startup.cs)Pages/_Host.cshtml (or _Host.razor) as the host page with Component Tag HelpersAddRazorComponents 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.| Input | Required | Description |
|---|---|---|
| Blazor Server project | Yes | The .csproj and source files of the Blazor Server app |
| Target framework | Yes | .NET 8 or later (e.g., net8.0, net9.0, net10.0) |
Program.cs or Startup.cs | Yes | The app's service and middleware configuration |
_Host.cshtml location | Recommended | Usually Pages/_Host.cshtml; may be _Host.razor in some projects |
Commit strategy: Commit after each logical step so the migration is reviewable and bisectable.
Update the .csproj file:
<TargetFramework>net8.0</TargetFramework>
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.
Routes.razor from App.razorThe 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.
Routes.razor in the project root.App.razor into Routes.razor.<CascadingAuthenticationState>, remove that wrapper (it will be replaced by a service in Step 5).App.razor empty for the next step.The resulting Routes.razor should look similar to:
<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.
_Host.cshtml to App.razorMove the HTML shell from Pages/_Host.cshtml into the now-empty App.razor and transform it from a Razor Page into a Razor component:
Remove Razor Page directives — delete @page "/", @using Microsoft.AspNetCore.Components.Web, @namespace, and @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers.
Add component injection — if using environment-conditional error UI, add:
@inject IHostEnvironment Env
Fix the base tag — replace <base href="~/" /> with <base href="/" />.
Replace HeadOutlet Component Tag Helper — replace:
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
with:
<HeadOutlet @rendermode="InteractiveServer" />
Replace App Component Tag Helper with Routes — replace:
<component type="typeof(App)" render-mode="ServerPrerendered" />
with:
<Routes @rendermode="InteractiveServer" />
Replace Environment Tag Helpers — replace:
<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:
@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:
<script src="_framework/blazor.server.js"></script>
with:
<script src="_framework/blazor.web.js"></script>
Add render mode import — add to _Imports.razor:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
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.
Program.csMake the following changes to Program.cs (or Startup.cs if the app uses the older hosting pattern):
Replace Blazor Server services — replace:
builder.Services.AddServerSideBlazor();
with:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
If AddServerSideBlazor had options configured (e.g., circuit options, hub options, detailed errors), migrate them to 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);
});
Replace Blazor endpoint mapping — replace:
app.MapBlazorHub();
with:
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.
Remove the fallback route — delete:
app.MapFallbackToPage("/_Host");
Remove explicit routing middleware — delete if present:
app.UseRouting();
Endpoint routing is the default and explicit UseRouting() is no longer needed.
Add antiforgery middleware — add after UseAuthentication/UseAuthorization if present:
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.
CascadingAuthenticationState (if present)If the app used <CascadingAuthenticationState> to wrap the router:
<CascadingAuthenticationState> component wrapper (already done in Step 2 if following this workflow).Program.cs:
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.
These are optional modernization improvements — not required for the conversion to work. If you suggest any of these, state explicitly that they are optional.
UseStaticFiles with MapStaticAssets (.NET 9+): app.MapStaticAssets() provides optimized static file serving with fingerprinting, pre-compression, and content-based ETags. See MapStaticAssets documentation.@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.<link> tag referenced a _Host assembly name; ensure it matches the project's actual assembly name: <link href="{AssemblyName}.styles.css" rel="stylesheet" />.AddServerSideBlazorMapBlazorHubMapFallbackToPageblazor.server.js_Host.cshtmlAddServerSideBlazor remainMapBlazorHub remainMapFallbackToPage("/_Host") remainblazor.server.js remainPages/_Host.cshtml has been deletedApp.razor serves as the root component with a full HTML document structureRoutes.razor contains the <Router> configurationProgram.cs uses AddRazorComponents().AddInteractiveServerComponents()Program.cs uses MapRazorComponents<App>().AddInteractiveServerRenderMode()app.UseAntiforgery() is present in the middleware pipeline<CascadingAuthenticationState>, it has been replaced with AddCascadingAuthenticationState() service registration| Pitfall | Solution |
|---|---|
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 name | If the <link href="{Name}.styles.css"> tag referenced the old project name, update it to match the current assembly name. |
@attribute [StreamRendering] for async data loading