Start Debugging

Migrar um app Blazor Server para Blazor United (Blazor Web App) no .NET 11

Uma lista de passos para mover um app Blazor Server independente para o template unificado Blazor Web App no .NET 11, mantendo cada página em InteractiveServer sem mudança de comportamento.

Se você tem um app Blazor Server independente (dotnet new blazorserver) e quer movê-lo para o template unificado que foi apelidado de “Blazor United” durante o ciclo de versão prévia do .NET 8 e que foi lançado como Blazor Web App, a migração é em sua maior parte mecânica e geralmente leva meio dia para um app pequeno, de um a três dias para um grande. Nada do código dos seus componentes precisa mudar. O que muda é o host: _Host.cshtml desaparece, o roteamento se move para um componente Routes, o Program.cs troca MapBlazorHub por MapRazorComponents, e você declara um render mode de forma explícita. Mantenha cada página em @rendermode InteractiveServer e o comportamento permanece idêntico ao do Blazor Server da era .NET 7. A única coisa que morde é a dupla execução do prerendering. Este guia tem como alvo o .NET 11 (versão prévia no momento da escrita, com GA programado para novembro de 2026) e o conjunto de pacotes Microsoft.AspNetCore.Components 11.0.x.

Por que sair do template Server independente

O template blazorserver independente ainda funciona no .NET 11, então esta não é uma migração forçada. Faça-a quando um destes for um resultado real que você deseja, não antes:

Se nenhum desses se aplica, uma mudança <TargetFramework>net11.0</TargetFramework> no seu app Server independente existente é uma alternativa válida e não é esta migração.

O que quebra

ÁreaMudançaSeveridade
Página hostPages/_Host.cshtml e _Layout.cshtml são substituídos por um componente host raiz App.razorhigh
Roteamento<Router> se move para fora do App.razor para um novo Routes.razorhigh
Inicialização em Program.csAddServerSideBlazor() + MapBlazorHub() + MapFallbackToPage("/_Host") substituídos por AddRazorComponents().AddInteractiveServerComponents() + MapRazorComponents<App>().AddInteractiveServerRenderMode()high
Render modeA interatividade é opt-in por componente ou global; não há um “o app inteiro é interativo” implícitohigh
PrerenderingOnInitialized/OnInitializedAsync executam duas vezes (passada de prerender + passada interativa) por padrãomedium
Script de cliente_framework/blazor.server.js passa a ser _framework/blazor.web.jsmedium
Cabeamento de autenticaçãoO componente CascadingAuthenticationState é substituído por AddCascadingAuthenticationState() na DImedium
Acesso ao HttpContextHttpContext só está disponível durante o SSR estático, não dentro de um componente interativomedium
Semântica do App.razorApp.razor não é mais o roteador; é o shell do documento HTMLlow

Lista pré-decolagem

Passos de migração

1. Suba o target framework e os pacotes

Edite o .csproj. O SDK continua sendo Microsoft.NET.Sdk.Web.

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

Suba qualquer referência de pacote Microsoft.AspNetCore.Components.* para 11.0.x. Remova Microsoft.AspNetCore.Components.Web se ele for referenciado explicitamente; ele faz parte da referência de framework para os projetos do SDK web.

Verifique: dotnet restore é concluído e dotnet build falha apenas com erros sobre a página host e o Program.cs (esperado neste ponto), não com erros de resolução de pacotes.

2. Crie o componente host App.razor

No template Server independente, o documento HTML vive em Pages/_Host.cshtml e Pages/_Layout.cshtml. Mova essa marcação para um novo App.razor raiz (delete primeiro o antigo roteador App.razor, ou renomeie-o). O tag helper <component> que inicializava a raiz passa a ser <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>

Duas mudanças fáceis de passar batido: o script é blazor.web.js (não blazor.server.js), e <HeadOutlet /> junto com <Routes /> são componentes, então adotam o render mode que você atribuir no passo 6.

Verifique: o arquivo compila como um componente Razor (sem diretiva @page, sem @model).

3. Mova o roteamento para Routes.razor

Crie Routes.razor na raiz do projeto e cole o bloco <Router> que costumava viver no antigo App.razor.

@* .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>

Verifique: um dotnet build não reclama mais de um tipo Routes ausente.

4. Habilite as abreviações de render mode em _Imports.razor

Adicione esta linha ao _Imports.razor para que você possa escrever InteractiveServer em vez de qualificá-lo por completo:

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

Verifique: @rendermode InteractiveServer em um componente é resolvido sem um erro de using.

5. Reescreva o Program.cs

Troque o registro do Blazor Server e os endpoints pelos equivalentes do Razor Components.

// .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();

Delete AddServerSideBlazor(), app.MapBlazorHub() e app.MapFallbackToPage("/_Host"). A chamada app.UseAntiforgery() é nova e obrigatória; o template Web App habilita o middleware de antiforgery por padrão e os envios de formulário falham sem ela.

Verifique: dotnet build é concluído com zero erros.

6. Torne o app interativo (render mode global)

A migração de menor risco torna o app inteiro InteractiveServer, reproduzindo o antigo comportamento exatamente. Defina o render mode em <Routes /> e <HeadOutlet /> dentro do App.razor:

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

Verifique: rode dotnet run, abra o app, e confirme que os componentes interativos (botões, @onclick, envios de EditForm) funcionam e que o navegador mantém um WebSocket aberto para /_blazor. Uma vez que isso funcione, mais tarde você pode rebaixar páginas individuais para @rendermode StaticServer ou escalar ilhas para WebAssembly, mas isso é trabalho pós-migração.

7. Lide com a dupla execução do prerendering

Esta é a única mudança de comportamento que surpreende as pessoas. Com um render mode interativo, o Blazor prerenderiza primeiro o HTML estático, depois renderiza de novo sobre o circuito ativo, então OnInitialized e OnInitializedAsync executam duas vezes. O antigo padrão do Server independente (render-mode="ServerPrerendered") tinha a mesma propriedade, mas muitos apps usavam render-mode="Server" e nunca a viam.

Você tem três opções. A mais limpa no .NET 11 é o atributo declarativo [PersistentState] (adicionado no .NET 10): faz o fetch uma vez durante o prerender, serializa no HTML, restaura na passada interativa.

// .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();
    }
}

Se você não quiser fazer o fetch durante o prerender de jeito nenhum, desabilite o prerendering para aquele limite:

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

Verifique: coloque um breakpoint ou uma linha de log em cada OnInitializedAsync com efeitos colaterais e confirme que ele executa uma vez por navegação real, não duas.

8. Refaça o cabeamento da autenticação e da autorização

Se o seu app usava <CascadingAuthenticationState> envolvendo o roteador, remova esse componente e registre-o na DI no lugar, depois troque RouteView por AuthorizeRouteView no Routes.razor.

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

Verifique: acesse uma página [Authorize] deslogado e confirme que você é redirecionado, depois faça login e confirme o acesso.

9. Delete os arquivos host mortos

Remova Pages/_Host.cshtml, Pages/_Layout.cshtml e qualquer chamada app.MapRazorPages() que existia apenas para servir o _Host. Remova AddRazorPages() do Program.cs se nada mais usa Razor Pages.

Verifique: dotnet build está limpo e o app ainda serve cada rota.

Verificação: o smoke test pós-migração

Rode todos estes antes de fazer merge:

Plano de rollback

Esta migração é reversível apenas se você manteve o branch do passo pré-decolagem. Uma vez que você deleta o _Host.cshtml e reescreve o Program.cs, não há um botão in loco para voltar ao modelo Server independente. Faça o rollback com checkout do commit pré-migração, não editando para frente. Como a mudança é estrutural e não uma migração de dados, não há nada a desfazer no seu banco de dados ou armazenamento. Crie um branch, faça o trabalho, verifique contra o smoke test, e faça merge apenas quando estiver no verde.

Tropeços que tivemos

O destino aqui é quase sempre “cada página em InteractiveServer, prerendering tratado”. Essa é a migração que não muda nada que o usuário possa ver. Adicionar páginas Static Server, ilhas WebAssembly e componentes Auto é a recompensa que você coleta depois, um componente de cada vez, sem mais agitação estrutural.

Relacionado

Fontes

Comments

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

< Voltar