Migre do .NET Framework 4.8 para o .NET 11 em 2026
Um manual de migração com versões fixadas para mover uma base de código .NET Framework 4.8 para o .NET 11 LTS em 2026, abrangendo a reescrita do csproj no formato SDK, System.Web para ASP.NET Core, WCF, EF6 para EF Core 11, remoção do BinaryFormatter, substituições de AppDomain e um plano de rollback realista.
Mover uma base de código .NET Framework 4.8 para o .NET 11 não é uma simples atualização de versão. É um exercício de re-plataformização que toca no formato do projeto, na pilha web, na camada de acesso a dados, no modelo de hospedagem e em uma longa cauda de APIs que silenciosamente desapareceram entre 2017 e 2026. A janela oficial de suporte do .NET Framework 4.8 ainda está aberta em 2026, mas o runtime não recebe uma atualização de recursos desde 2019, todo plano moderno do Azure App Service está na pilha Core por padrão e quase todo pacote NuGet que vale a pena depender abandonou os alvos net48. O esforço realista para um aplicativo típico de linha de negócio é de duas a seis semanas para um serviço pequeno, e de dois a quatro meses para uma base de código média com WCF, EF6 e um front end WebForms ou MVC 5. Aplicativos WebForms permanecem onde estão ou são reescritos; não há porte no local. Este post fixa net48 como origem e net11.0 como alvo, e assume que o projeto está no Windows.
Por que migrar agora
- O .NET Framework 4.8 está em manutenção apenas desde o lançamento do 4.8.1 em agosto de 2022. Sem novas APIs, sem novos recursos de linguagem C#. O runtime do .NET 11 entrega PGO dinâmico ativado por padrão, o JIT em camadas modernizado e Native AOT para APIs mínimas do ASP.NET Core.
- Todo framework novo da Microsoft (Aspire, Microsoft Agent Framework, Semantic Kernel 1.x, Azure Functions isolated worker) tem como alvo
net8.0ou mais recente e não será retroportado. Permanecer no 4.8 significa que nenhum deles está acessível a partir do seu próprio processo. - Custo de nuvem. O consumo de memória do runtime do .NET 11 para uma API mínima ASP.NET Core ociosa é aproximadamente 35 a 50 por cento de um processo worker do ASP.NET 4.8 em carga comparável, o que se traduz diretamente em planos do App Service menores ou maior densidade de pods no Kubernetes.
- Contratação e ferramentas. Analisadores Roslyn, geradores de código-fonte e a CLI moderna
dotnetassumem um projeto no formato SDK. A versão da linguagem C# emnet48é limitada ao C# 7.3 a menos que você lute contra o compilador, o que deixa uma década de recursos de linguagem fora do alcance.
Se a base de código é um aplicativo desktop Windows (WinForms ou WPF) que só roda no Windows e não tem planos de implantar em outro lugar, a pergunta é justa. A resposta ainda costuma ser sim, porque a vida útil suportada do net48 termina com a janela de suporte estendido do Windows 10, e o suporte de ferramentas já está enfraquecido.
O que quebra
| Área | Mudança | Severidade |
|---|---|---|
| Formato do projeto | packages.config e o XML antigo do csproj não são suportados pelo SDK do .NET 11 | alta |
System.Web | Removido inteiramente. HttpContext.Current, módulos, handlers, WebForms não têm equivalente no .NET 11 | alta |
| Servidor WCF | System.ServiceModel do lado servidor não é suportado. Use CoreWCF ou reescreva para gRPC ou HTTP | alta |
| Cliente WCF | Suportado via pacotes NuGet System.ServiceModel.* 6.x, com bindings limitados | média |
| Entity Framework 6 | Roda no .NET 11 com EF6 6.5.0 ou posterior, mas novo desenvolvimento deve usar EF Core 11 | média |
AppDomain | Apenas o AppDomain padrão existe. Sem CreateDomain, sem contêineres de plugin descarregáveis | alta |
BinaryFormatter | Removido no .NET 9, sem chave de opt-in | alta |
| .NET Remoting | Foi-se. Sem substituto; reescreva para um protocolo de rede que você realmente queira | alta |
| Code Access Security | Foi-se. [SecurityCritical], PermissionSet, sandboxing todos removidos | alta |
web.config | A configuração move para appsettings.json. As seções system.web não se aplicam | alta |
app.config | A maioria das configurações ainda funciona via Microsoft.Extensions.Configuration.Xml, mas redirecionamentos de binding foram-se | média |
| WPF e WinForms | Suportados no .NET 11, apenas Windows. A maioria dos controles de terceiros precisa de uma build 6.x ou posterior | média |
System.Drawing.Common | Suporte multiplataforma removido no .NET 6. Apenas Windows desde então | média |
Leia o .NET Framework to .NET porting overview e a .NET 11 breaking changes list uma vez antes de tocar em um .csproj. A primeira lista é de longe a mais extensa das duas.
Checklist pré-decolagem
Execute essas etapas antes de mudar um único arquivo de projeto.
- Instale o SDK do .NET 11 em cada máquina de desenvolvimento e runner de CI. Verifique com
dotnet --list-sdkse confirme que11.0.xaparece. Mantenha o developer pack do .NET Framework 4.8 instalado para que a solução antiga ainda abra no Visual Studio. - Instale a CLI do .NET Upgrade Assistant e execute-a primeiro em modo de análise. Ela não migra código; ela produz um relatório acionável.
# .NET 11, upgrade-assistant 0.6.x dotnet tool install --global upgrade-assistant upgrade-assistant analyze MySolution.sln - Execute o .NET Portability Analyzer ou
apiportcontra os assemblies compilados. Qualquer coisa marcada como “not portable” é trabalho de migração que a ferramenta upgrade-assistant não fará por você. - Capture uma linha de base. Execute a suíte de testes existente no .NET Framework 4.8 e armazene o resultado. Um verde limpo no runtime antigo significa que o primeiro vermelho no .NET 11 é, sem ambiguidade, uma regressão de migração.
- Inventarie pacotes NuGet de terceiros. Qualquer coisa que entregue apenas assemblies
net48ounet472é um bloqueador. Substituições:log4net2.0.16,Newtonsoft.Json13.x,AutoMapper13.x,Dapper2.1.x todos multi-target e funcionam no .NET 11. Qualquer outra coisa precisa de um ticket de atualização contra o fornecedor. - Ramifique a migração. Planeje pelo menos um PR por projeto, e um PR separado para os projetos de teste. Um único mega-PR para uma base de código média é não-revisável.
Etapas de migração
-
Converta cada
.csprojpara o formato SDK. Substitua o XML antigo pelo cabeçalho no formato SDK. O novo formato infere arquivos, descarta a maioria das referências de assembly e usaPackageReferenceem vez depackages.config. A ferramentatry-convert(incluída no .NET Upgrade Assistant) lida com a parte mecânica.<!-- src/MyApi.csproj, .NET 11, after conversion --> <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net11.0</TargetFramework> <LangVersion>14.0</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="11.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="11.0.0" /> </ItemGroup> </Project>Verificação:
dotnet buildsai com erros que nomeiam APIs removidas em vez de erros de parser contra o próprio arquivo de projeto. Apaguepackages.configapós a conversão ser bem-sucedida. -
Apague o uso de
BinaryFormatterem todos os lugares. O tipo foi removido no .NET 9 sem chave de compatibilidade. Substitua-o porSystem.Text.Json, MessagePack ouprotobuf-netdependendo se você precisa de um formato JSON ou binário no fio. Se você tem blobs armazenados serializados comBinaryFormatter, escreva um utilitário de conversão de uso único que ainda rode no .NET Framework 4.8 para traduzi-los para o novo formato antes de desativar o ambiente antigo. Fazer essa conversão a partir do .NET 11 não é possível.Verificação:
grep -r "BinaryFormatter" src/está vazio. Qualquer armazenamento de blob que anteriormente continha payloads binário-formatados foi re-serializado e o novo formato faz round-trip através de um teste unitário. -
Reescreva a pilha web de
System.Webpara ASP.NET Core 11. Esta é a maior peça única de trabalho. Controllers MVC 5 mapeiam quase um-para-um para controllers do ASP.NET Core, mas atributos de roteamento, model binding, action filters e injeção de dependência diferem todos.HttpContext.Currentfoi-se; controllers e middleware recebemHttpContextexplicitamente.Application_StartemGlobal.asaxtorna-se código de inicialização emProgram.cs. Controllers WebAPI 2 são muito próximos de controllers ASP.NET Core, mas herdam deControllerBaseem vez deApiController.// Program.cs, .NET 11, C# 14 var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddOpenApi(); builder.Services.AddDbContext<AppDb>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapOpenApi(); app.Run();WebForms (
.aspx) não tem caminho de migração. Ou mantenha o aplicativo WebForms no .NET Framework 4.8 pelo resto de sua vida por trás de um proxy reverso, ou reescreva as páginas afetadas como Blazor, MVC ou Razor Pages. A comparação Blazor Server vs Blazor WebAssembly vs Blazor United é o ponto de partida certo se Blazor está sobre a mesa.Verificação: cada controller tem pelo menos um teste de integração que exercita uma rota HTTP contra
WebApplicationFactory<Program>e afirma tanto o código de status quanto o corpo da resposta. -
Mova
web.configparaappsettings.json. Connection strings, appSettings customizados e configuração de logging movem para JSON. Seçõessystem.webnão se aplicam. Seçõessystem.webServerque configuram o IIS ainda se aplicam se você hospedar atrás do IIS via o módulo in-process, mas a maioria das implantações de produção agora usa Kestrel diretamente. Configurações de autenticação movem das seções<authentication>e<authorization>do web.config parabuilder.Services.AddAuthentication(...)e a API de política de autorização.// appsettings.json, .NET 11 { "ConnectionStrings": { "Default": "Server=.;Database=App;Trusted_Connection=True;TrustServerCertificate=True;" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } }Verificação: o aplicativo lê cada valor anteriormente dirigido por configuração através de
IConfigurationou do padrão fortemente tipadoIOptions<T>; nenhuma chamada paraConfigurationManager.AppSettingssobrevive no código de produção. -
Lide com WCF. WCF do lado servidor não é suportado no .NET 11. Dois caminhos realistas:
- CoreWCF (o porte mantido pela comunidade). Adicione
CoreWCF.Primitives,CoreWCF.Httpe os bindings que você realmente usa. A maioria dos serviçosBasicHttpBindingeNetTcpBindingmigra com uma referência de contrato e uma chamada de configuraçãoUseServiceModel. Streaming, transações e segurança em nível de mensagem têm graus variados de suporte; verifique a CoreWCF compatibility matrix antes de se comprometer. - Reescreva para gRPC ou uma API HTTP. Teto mais alto, mais trabalho. A escolha certa quando a interface WCF só foi consumida por clientes que você controla.
WCF do lado cliente é suportado via os pacotes NuGet
System.ServiceModel.*6.x comBasicHttpBinding,NetTcpBinding(limitado) eWSHttpBinding(apenas segurança de transporte). Se seu cliente usaWSFederationHttpBindingou segurança em nível de mensagem, você vai reescrever o consumidor.Verificação: cada endpoint WCF tem um teste de contrato que executa o cliente .NET 11 contra o novo host CoreWCF ou o substituto reescrito, e afirma os mesmos payloads que o cliente .NET Framework antigo.
- CoreWCF (o porte mantido pela comunidade). Adicione
-
Mova Entity Framework 6 para EF Core 11 (quando valer a pena). EF6 roda no .NET 11 via o pacote
EntityFramework6.5.0, então um lift-and-shift estrito é possível. Mas EF6 não recebe novos recursos, a APIDbContextno EF Core está mais próxima do que você quer para injeção de dependência do ASP.NET Core, e as consultas compiladas do EF Core 11 e a tradução de coleções primitivas são significativamente mais rápidas. Para a maioria das equipes, a escolha certa é entregar a migração no EF6 primeiro, depois mover para EF Core em um PR de acompanhamento. O post EF Core compiled queries vs raw SQL vs Dapper quantifica os ganhos no caminho quente.Verificação: o ORM escolhido passa na mesma suíte de testes de integração que rodava sob EF6 no .NET Framework, incluindo quaisquer testes que afirmavam o SQL gerado.
-
Substitua os carregadores de plugin
AppDomain.CreateDomain.AppDomainnão é mais um limite de isolamento no .NET 11; apenas o domínio padrão existe. Sistemas de plugin que anteriormente carregavam assemblies em umAppDomainfilho para semântica de descarregamento ou isolamento de falhas devem mover paraAssemblyLoadContextcomisCollectible: truee chamarUnload()quando terminado. Plugins fora-de-processo via processos workerdotnetsão o padrão mais seguro quando o plugin é não confiável.Verificação: um teste unitário carrega um assembly de plugin, chama nele, descarrega o
AssemblyLoadContexte afirma que umaWeakReferenceao contexto torna-senullapós um cicloGC.Collect(). -
Audite os saltos de versão da linguagem C#. Ir do C# 7.3 para o C# 14 é doze lançamentos de linguagem em um passo. A maior parte é aditiva e segura, mas tipos de referência anuláveis (introduzidos no C# 8) vão sinalizar milhares de avisos em código legado se você ligar
<Nullable>enable</Nullable>globalmente. O caminho realista é<Nullable>annotations</Nullable>primeiro (apenas anotações, sem diagnósticos), depois conversão arquivo por arquivo usando pragmas#nullable enable. A mudança de resolução de sobrecarga do C# 14 em torno de sobrecargas de span está documentada no post de correção C# 14 overload resolution breaking change with spans.Verificação:
dotnet build -warnaserrorestá limpo no escopo anulável acordado antes do merge. -
Atualize as imagens dos runners de CI. Suba o
actions/setup-dotnetdo GitHub Actions paradotnet-version: 11.0.x, atualize qualquer imagem base do Dockerfile paramcr.microsoft.com/dotnet/sdk:11.0emcr.microsoft.com/dotnet/aspnet:11.0, e remova a imagem MSBuild antiga do .NET Framework. Se algum projeto ainda tem que compilar sob .NET Framework (por exemplo, a ferramenta de conversão única doBinaryFormatterda etapa 2), mantenha um único runnerwindows-2022com o developer pack do .NET Framework 4.8 instalado e proteja-o atrás de um filtro de caminho.Verificação: uma execução de pipeline em um branch de feature está verde de ponta a ponta, incluindo
dotnet publish, build de imagem de contêiner e deploy de fumaça em um ambiente de staging.
Verificação (checklist de fumaça)
Após as etapas acima, o aplicativo deve passar cada linha desta lista antes do PR de migração entrar em merge:
dotnet --list-sdksmostra11.0.xedotnet --versiona partir da raiz do repositório imprime11.0.x.dotnet restore && dotnet build -c Releasesai com 0 e zero avisos no escopo anulável que você acordou.dotnet test -c Releaseestá verde e a contagem de testes corresponde (ou excede) à linha de base do .NET Framework 4.8.dotnet publish -c Releaseproduz um artefato self-contained que inicializa em um host de staging limpo sem o redistribuível do .NET Framework 4.8 instalado.- Cada rota HTTP tem pelo menos um teste de integração contra
WebApplicationFactory<Program>. - Os logs não mostram referências de primeira chance a
BinaryFormatter,IWebHostBuilder,HttpContext.CurrentouConfigurationManagerem caminhos de código de produção. - Um deploy de staging serve o caminho dourado; latência p50/p95 está dentro de 20 por cento da linha de base do .NET Framework em hardware equivalente.
Se qualquer um deles falhar, pare. Uma migração parcial para .NET 11 é pior que um deploy limpo do .NET Framework 4.8 porque compromete com os dois runtimes.
Rollback
A migração é reversível apenas enquanto o esquema do banco de dados e quaisquer formatos no fio não tenham mudado. A troca de runtime em si é reversível: reverta as mudanças do .csproj e reinstale o redistribuível do .NET Framework 4.8 no host. As decisões que tornam o rollback caro são geralmente ortogonais:
- Uma nova migração do EF Core 11 rodou contra o banco de dados de produção. Faça rollback do esquema primeiro.
- Payloads JSON foram re-serializados sob padrões do
System.Text.Jsonque diferem doNewtonsoft.Json. Consumidores downstream que fazem pattern-match em ordenação de campos ou tratamento de null verão drift. - A autenticação mudou de cookies
FormsAuthenticationpara os cookies de data-protection do ASP.NET Core. Sessões existentes são invalidadas de qualquer jeito.
O plano pragmático é manter o deploy do .NET Framework quente em um slot separado por uma semana após o cutover, e proteger o cutover atrás de uma feature flag no load balancer em vez de no momento do deploy. Corrija para frente depois dessa janela.
Pegadinhas que encontramos
HttpClientno .NET 11 impõe estritamente a indicação de nome de servidor TLS. Chamadas para serviços internos que apresentam um certificado sem um SAN correspondente falham comAuthenticationException. Ou conserte o certificado ou definaSslOptions.RemoteCertificateValidationCallbackdeliberadamente. Os padrões do .NET Framework eram mais frouxos, e isso mascarava a lacuna do SAN.DateTime.Parseno .NET 11 é mais estrito sobre formatos ambíguos que o .NET Framework 4.8. Código que fazia round-trip deDateTimeatravés de string sem umIFormatProviderexplícito vai começar a lançarFormatExceptionem entradas que aceitava antes. Sempre passeCultureInfo.InvariantCulturee um formato conhecido. A correção JSON value could not be converted to System.DateTime cobre a variante mais comum quando a data chega através de JSON.Microsoft.Data.SqlClientsubstituiSystem.Data.SqlClientem qualquer caminho moderno. EF Core 11 querMicrosoft.Data.SqlClient7.x ou posterior. Um pin transitivo noSystem.Data.SqlClientantigo vai compilar mas falhar em runtime na negociação TLS 1.3 contra caixas SQL Server mais novas.- A vinculação de configuração diferencia maiúsculas de minúsculas no JSON, mas não no
app.config. Uma propriedade chamadaMaxRetriesemappsettings.jsonnão vincula a partir de uma chavemaxretries. OConfigurationManagerdo .NET Framework não se importava. HostingEnvironment.MapPathfoi-se. Substitua porIWebHostEnvironment.ContentRootPathePath.Combine. A sintaxe de caminho virtual~/não é entendida por nada no ASP.NET Core.- Surrogates de datacontract do WCF não fazem round-trip identicamente através do CoreWCF. Se você depende de
IDataContractSurrogate, escreva um teste de contrato que afirma o formato exato no fio antes e depois da migração, não apenas igualdade de objetos.
Relacionados
- Migre do .NET 8 para o .NET 11: o checklist completo
- Native AOT vs ReadyToRun vs JIT no .NET 11
- APIs mínimas vs controllers no ASP.NET Core 11
- System.Text.Json vs Newtonsoft.Json em 2026
- EF Core 11 vs Dapper para inserções em massa: benchmark real
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.