Start Debugging

Eine Blazor-Server-App in .NET 11 zu Blazor United (Blazor Web App) migrieren

Eine Schritt-für-Schritt-Checkliste, um eine eigenständige Blazor-Server-App auf das vereinheitlichte Blazor-Web-App-Template in .NET 11 zu verschieben und jede Seite ohne Verhaltensänderung auf InteractiveServer zu halten.

Wenn Sie eine eigenständige Blazor-Server-App (dotnet new blazorserver) haben und sie auf das vereinheitlichte Template verschieben möchten, das während des Preview-Zyklus von .NET 8 den Spitznamen “Blazor United” trug und als Blazor Web App veröffentlicht wurde, ist die Migration größtenteils mechanisch und dauert in der Regel einen halben Tag für eine kleine App, ein bis drei Tage für eine große. Am Code Ihrer Komponenten muss sich nichts ändern. Was sich ändert, ist der Host: _Host.cshtml verschwindet, das Routing wandert in eine Routes-Komponente, Program.cs tauscht MapBlazorHub gegen MapRazorComponents, und Sie deklarieren einen Render Mode explizit. Halten Sie jede Seite auf @rendermode InteractiveServer, und das Verhalten bleibt identisch zu Blazor Server der .NET-7-Ära. Das Einzige, was beißt, ist die doppelte Ausführung des Prerenderings. Dieser Leitfaden zielt auf .NET 11 (zum Zeitpunkt des Schreibens Preview, GA geplant für November 2026) und den Paketsatz Microsoft.AspNetCore.Components 11.0.x ab.

Warum das eigenständige Server-Template verlassen

Das eigenständige blazorserver-Template funktioniert in .NET 11 weiterhin, dies ist also keine erzwungene Migration. Führen Sie sie durch, wenn eines davon ein echtes Ergebnis ist, das Sie wollen, nicht vorher:

Falls nichts davon zutrifft, ist eine <TargetFramework>net11.0</TargetFramework>-Änderung an Ihrer bestehenden eigenständigen Server-App eine gültige Alternative und nicht diese Migration.

Was bricht

BereichÄnderungSchweregrad
Host-SeitePages/_Host.cshtml und _Layout.cshtml werden durch eine Root-Host-Komponente App.razor ersetzthigh
Routing<Router> wandert aus App.razor in ein neues Routes.razorhigh
Start in Program.csAddServerSideBlazor() + MapBlazorHub() + MapFallbackToPage("/_Host") ersetzt durch AddRazorComponents().AddInteractiveServerComponents() + MapRazorComponents<App>().AddInteractiveServerRenderMode()high
Render ModeInteraktivität ist Opt-in pro Komponente oder global; kein implizites “die ganze App ist interaktiv”high
PrerenderingOnInitialized/OnInitializedAsync laufen standardmäßig zweimal (Prerender-Durchlauf + interaktiver Durchlauf)medium
Client-Skript_framework/blazor.server.js wird zu _framework/blazor.web.jsmedium
Auth-VerdrahtungDie Komponente CascadingAuthenticationState wird durch AddCascadingAuthenticationState() in der DI ersetztmedium
HttpContext-ZugriffHttpContext ist nur während des statischen SSR verfügbar, nicht innerhalb einer interaktiven Komponentemedium
App.razor-SemantikApp.razor ist nicht mehr der Router; es ist die Hülle des HTML-Dokumentslow

Checkliste vor dem Start

Migrationsschritte

1. Target Framework und Pakete anheben

Bearbeiten Sie die .csproj. Das SDK bleibt Microsoft.NET.Sdk.Web.

<!-- .NET 11 -->
<PropertyGroup>
  <TargetFramework>net11.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Heben Sie alle Microsoft.AspNetCore.Components.*-Paketreferenzen auf 11.0.x an. Entfernen Sie Microsoft.AspNetCore.Components.Web, falls es explizit referenziert wird; es ist Teil der Framework-Referenz für Web-SDK-Projekte.

Prüfen: dotnet restore läuft durch und dotnet build scheitert nur mit Fehlern zur Host-Seite und Program.cs (an diesem Punkt erwartet), nicht mit Fehlern bei der Paketauflösung.

2. Die App.razor-Host-Komponente erstellen

Im eigenständigen Server-Template lebt das HTML-Dokument in Pages/_Host.cshtml und Pages/_Layout.cshtml. Verschieben Sie dieses Markup in ein neues Root-App.razor (löschen Sie zuerst den alten App.razor-Router oder benennen Sie ihn um). Der <component>-Tag-Helper, der die Root gebootet hat, wird zu <Routes />.

@* .NET 11 - App.razor is now the HTML document shell, not the router *@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="YourApp.styles.css" />
    <HeadOutlet />
</head>
<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>
</html>

Zwei leicht zu übersehende Änderungen: das Skript ist blazor.web.js (nicht blazor.server.js), und <HeadOutlet /> zusammen mit <Routes /> sind Komponenten, übernehmen also den Render Mode, den Sie ihnen in Schritt 6 zuweisen.

Prüfen: die Datei kompiliert als Razor-Komponente (keine @page-Direktive, kein @model).

3. Das Routing nach Routes.razor verschieben

Erstellen Sie Routes.razor im Projekt-Root und fügen Sie den <Router>-Block ein, der früher im alten App.razor lebte.

@* .NET 11 - Routes.razor holds the router that used to be in App.razor *@
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(Layout.MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Prüfen: ein dotnet build beschwert sich nicht mehr über einen fehlenden Routes-Typ.

4. Render-Mode-Kurzformen in _Imports.razor aktivieren

Fügen Sie diese Zeile zu _Imports.razor hinzu, damit Sie InteractiveServer schreiben können, statt es vollständig zu qualifizieren:

@* .NET 11 *@
@using static Microsoft.AspNetCore.Components.Web.RenderMode

Prüfen: @rendermode InteractiveServer in einer Komponente löst sich ohne einen Using-Fehler auf.

5. Program.cs neu schreiben

Tauschen Sie die Blazor-Server-Registrierung und die Endpunkte gegen die Razor-Components-Entsprechungen.

// .NET 11, C# 14 - Blazor Web App startup
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

// keep your existing app services here (DbContext, HttpClient, etc.)

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();

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

app.Run();

Löschen Sie AddServerSideBlazor(), app.MapBlazorHub() und app.MapFallbackToPage("/_Host"). Der Aufruf app.UseAntiforgery() ist neu und erforderlich; das Web-App-Template aktiviert die Antiforgery-Middleware standardmäßig, und Formular-Posts scheitern ohne sie.

Prüfen: dotnet build läuft mit null Fehlern durch.

6. Die App interaktiv machen (globaler Render Mode)

Die risikoärmste Migration macht die gesamte App InteractiveServer und reproduziert das alte Verhalten exakt. Setzen Sie den Render Mode auf <Routes /> und <HeadOutlet /> innerhalb von App.razor:

@* .NET 11 - global InteractiveServer, matches standalone Blazor Server behaviour *@
<HeadOutlet @rendermode="InteractiveServer" />
...
<Routes @rendermode="InteractiveServer" />

Prüfen: führen Sie dotnet run aus, öffnen Sie die App und bestätigen Sie, dass interaktive Komponenten (Schaltflächen, @onclick, EditForm-Submits) funktionieren und der Browser einen offenen WebSocket zu /_blazor hält. Sobald das funktioniert, können Sie später einzelne Seiten auf @rendermode StaticServer herabstufen oder Inseln auf WebAssembly hochstufen, aber das ist Arbeit nach der Migration.

7. Die doppelte Ausführung des Prerenderings behandeln

Dies ist die eine Verhaltensänderung, die Leute überrascht. Mit einem interaktiven Render Mode prerendert Blazor zuerst statisches HTML, rendert dann erneut über den Live-Circuit, daher laufen OnInitialized und OnInitializedAsync zweimal. Der alte Standard des eigenständigen Servers (render-mode="ServerPrerendered") hatte dieselbe Eigenschaft, aber viele Apps verwendeten render-mode="Server" und sahen es nie.

Sie haben drei Optionen. Die sauberste in .NET 11 ist das deklarative [PersistentState]-Attribut (in .NET 10 hinzugefügt): einmal während des Prerenders abrufen, in das HTML serialisieren, im interaktiven Durchlauf wiederherstellen.

// .NET 11 - fetch once, survive the prerender-to-interactive handoff
public partial class Dashboard : ComponentBase
{
    [PersistentState]
    public List<Order>? Orders { get; set; }

    [Inject] public required IOrderService OrderService { get; init; }

    protected override async Task OnInitializedAsync()
    {
        // Orders is non-null on the interactive pass: state was restored,
        // so the service is not hit a second time.
        Orders ??= await OrderService.GetRecentAsync();
    }
}

Wenn Sie während des Prerenders überhaupt nicht abrufen wollen, deaktivieren Sie das Prerendering für diese Grenze:

@* .NET 11 - skip the prerender pass entirely for this component tree *@
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Prüfen: setzen Sie einen Breakpoint oder eine Log-Zeile in jede OnInitializedAsync mit Seiteneffekten und bestätigen Sie, dass sie einmal pro echter Navigation läuft, nicht zweimal.

8. Authentifizierung und Autorisierung neu verdrahten

Falls Ihre App <CascadingAuthenticationState> um den Router gewickelt hatte, entfernen Sie diese Komponente und registrieren Sie sie stattdessen in der DI, dann tauschen Sie RouteView gegen AuthorizeRouteView in Routes.razor.

// .NET 11 - Program.cs
builder.Services.AddCascadingAuthenticationState();
@* .NET 11 - Routes.razor, authorized routing *@
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />

Prüfen: rufen Sie eine [Authorize]-Seite im abgemeldeten Zustand auf und bestätigen Sie, dass Sie umgeleitet werden, dann melden Sie sich an und bestätigen Sie den Zugriff.

9. Die toten Host-Dateien löschen

Entfernen Sie Pages/_Host.cshtml, Pages/_Layout.cshtml und jeden app.MapRazorPages()-Aufruf, der nur existierte, um _Host zu bedienen. Entfernen Sie AddRazorPages() aus Program.cs, falls nichts anderes Razor Pages verwendet.

Prüfen: dotnet build ist sauber und die App bedient weiterhin jede Route.

Verifizierung: der Smoke-Test nach der Migration

Führen Sie all dies aus, bevor Sie mergen:

Rollback-Plan

Diese Migration ist nur dann reversibel, wenn Sie den Branch aus dem Schritt vor dem Start behalten haben. Sobald Sie _Host.cshtml löschen und Program.cs neu schreiben, gibt es keinen In-Place-Schalter zurück zum eigenständigen Server-Modell. Rollen Sie zurück, indem Sie den Commit vor der Migration auschecken, nicht indem Sie vorwärts editieren. Da die Änderung strukturell und keine Datenmigration ist, gibt es nichts in Ihrer Datenbank oder Ihrem Speicher rückgängig zu machen. Branch erstellen, die Arbeit erledigen, gegen den Smoke-Test verifizieren und erst mergen, wenn es grün ist.

Stolpersteine, auf die wir gestoßen sind

Das Ziel hier ist fast immer “jede Seite auf InteractiveServer, Prerendering behandelt”. Das ist die Migration, die nichts ändert, was der Benutzer sehen kann. Static-Server-Seiten, WebAssembly-Inseln und Auto-Komponenten hinzuzufügen ist die Belohnung, die Sie danach einsammeln, eine Komponente nach der anderen, ohne weitere strukturelle Umwälzung.

Verwandt

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück