How to Migrate from .NET Framework to .NET 10 (formerly .NET Core)
How do I migrate from .NET Framework to .NET Core/.NET 10?
TL;DR
- Bottom line: Migrate incrementally using the Strangler Fig pattern -- set up an ASP.NET Core app as a YARP proxy in front of your .NET Framework app, then move routes one at a time. Target .NET 10 (LTS, supported until November 10, 2028) and use the GitHub Copilot app modernization agent for AI-assisted code fixes. As of March 12, 2026 the agent runs across Visual Studio, VS Code, GitHub Copilot CLI, and GitHub.com -- the previous Visual-Studio-only restriction is gone. [src10]
- Key tool/command: GitHub Copilot Chat with the
modernize-dotnetagent (@modernize-dotnet upgrade this solution to .NET 10). For the deprecated legacy CLI:dotnet tool install -g upgrade-assistant && upgrade-assistant upgrade <project.csproj>(still functional in VS 2026 via Tools > Options > Projects and Solutions > Modernization > "Enable legacy Upgrade Assistant"). - Watch out for: Technologies with no .NET Core equivalent -- WCF server-side, Web Forms, Windows Workflow Foundation, and AppDomains are not available in modern .NET. BinaryFormatter is fully removed in .NET 10.
- Works with: .NET Framework 4.5+ to .NET 8/9/10, Windows Forms, WPF, ASP.NET MVC/WebAPI, console apps, class libraries.
- Latest servicing: .NET 10.0.8 (May 12, 2026) -- pin to the newest patch when starting a migration. [src11]
Constraints
- Target .NET 10 (LTS, November 10, 2028 EOL) for all new migrations. After Microsoft extended STS support from 18 to 24 months, both .NET 8 LTS and .NET 9 STS now end on the same day -- November 10, 2026 -- so neither is a long-term destination. [src8]
- Web Forms, Windows Workflow Foundation (WF), and AppDomains have NO direct equivalent in modern .NET. These require a full rewrite. [src1]
- BinaryFormatter is completely removed in .NET 10 (disabled-by-default in .NET 8, removed in .NET 9+). All serialization must use System.Text.Json, MessagePack, or Protocol Buffers. [src9]
- The .NET Upgrade Assistant CLI is deprecated as of 2025. Use the GitHub Copilot app modernization agent (cross-platform across Visual Studio, VS Code, GitHub Copilot CLI, and GitHub.com since 2026-03-12; .NET Framework migrations still require Windows). Requires a paid Copilot SKU. [src4, src10]
System.Drawing.CommonthrowsPlatformNotSupportedExceptionon Linux/macOS in .NET 6+. Use SkiaSharp or ImageSharp for cross-platform. [src1]- Code Access Security (CAS) and Security Transparency are not supported in .NET Core. Use OS-level security boundaries instead. [src1]
Quick Reference
| .NET Framework Pattern | .NET 10 Equivalent | Notes |
|---|---|---|
System.Web.HttpContext | Microsoft.AspNetCore.Http.HttpContext | Completely different API -- use System.Web adapters for incremental migration [src5] |
Web.config (XML) | appsettings.json + Options pattern | Use IOptions<T> / IConfiguration for strongly-typed settings [src6] |
Global.asax / Application_Start | Program.cs / WebApplication.CreateBuilder() | Minimal hosting model in .NET 6+; Startup.cs optional [src3] |
HTTP Modules (IHttpModule) | Middleware (IMiddleware / convention) | Register in app.UseMiddleware<T>(); order matters [src6] |
HTTP Handlers (IHttpHandler) | Middleware or MapGet/MapPost endpoints | Use minimal APIs or controller endpoints [src6] |
packages.config | PackageReference in .csproj | Migrate via VS right-click > "Migrate packages.config to PackageReference" [src1] |
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> | <TargetFramework>net10.0</TargetFramework> | SDK-style project format required [src1] |
| WCF (server-side) | gRPC, CoreWCF 2.x, or REST API | CoreWCF 2.x supports .NET 8/10; gRPC recommended for new services [src7] |
| Entity Framework 6 | Entity Framework Core 10 | Different API surface; migrations not directly compatible [src2] |
System.Web.Mvc.Controller | Microsoft.AspNetCore.Mvc.Controller | Namespace swap; action filters and model binding differ [src3] |
FormsAuthentication | ASP.NET Core Identity / Cookie Auth | Use AddAuthentication().AddCookie(); passkey auth in .NET 10 [src3, src8] |
System.Drawing (GDI+) | SkiaSharp or ImageSharp | System.Drawing.Common is Windows-only in .NET 6+ [src1] |
AppDomain.CreateDomain() | Separate process or AssemblyLoadContext | AppDomains not supported; use containers for isolation [src1] |
.NET Remoting | gRPC, REST, System.IO.Pipes, or StreamJsonRpc | Remoting calls throw PlatformNotSupportedException [src1] |
BundleConfig.cs (bundling) | Webpack, Vite, or dotnet bundle | Built-in bundling removed; use standard frontend tooling [src3] |
BinaryFormatter | System.Text.Json or MessagePack | BinaryFormatter fully removed in .NET 10 [src9] |
Decision Tree
START
|-- Is the app ASP.NET Web Forms?
| |-- YES -> No direct port path. Rewrite in Blazor Server/WASM or Razor Pages [src1]
| +-- NO v
|-- Is the app a class library or console app?
| |-- YES -> Convert to SDK-style project, retarget to net10.0, fix API incompatibilities [src1]
| +-- NO v
|-- Is the app ASP.NET MVC / Web API?
| |-- YES -> Is it a large production app (>50K LOC)?
| | |-- YES -> Use incremental migration (YARP proxy + Strangler Fig) [src5]
| | +-- NO -> Use GitHub Copilot app modernization or legacy Upgrade Assistant [src4]
| +-- NO v
|-- Is it WinForms or WPF?
| |-- YES -> Convert to SDK-style project, retarget to net10.0-windows [src1]
| +-- NO v
|-- Does it use WCF server-side?
| |-- YES -> Port to CoreWCF 2.x (API-compatible) or rewrite as gRPC/REST [src7]
| +-- NO v
+-- DEFAULT -> Analyze with GitHub Copilot app modernization or legacy Upgrade Assistant [src4]
Decision Logic
If targeting a NEW migration in 2026 with at least 18 months of maintenance ahead
Target .NET 10 directly. Skip .NET 8 and .NET 9 -- both end support on November 10, 2026. [src8, src11]
If the app is ASP.NET MVC or Web API and larger than ~50K lines of code
Use the incremental approach: an ASP.NET Core host with YARP reverse proxy and Microsoft.AspNetCore.SystemWebAdapters for shared session/auth, migrating one route at a time. [src5]
If the app is ASP.NET Web Forms
Do NOT attempt a port. Rewrite as Blazor Server, Blazor United, or ASP.NET Core Razor Pages. There is no automated migration path. [src1]
If the app depends on WCF server-side
Port to CoreWCF 2.x for binary client compatibility; rewrite as gRPC or REST only if you control the clients. [src7]
If the app uses BinaryFormatter for caching or serialization
Replace it BEFORE retargeting to .NET 10 -- BinaryFormatter is compile-time removed in .NET 10. Use System.Text.Json for safe payloads or MessagePack/protobuf for binary throughput. [src9]
If the team has a paid GitHub Copilot subscription
Use the modernize-dotnet Copilot agent (VS, VS Code, Copilot CLI, or GitHub.com since March 2026). Always re-run dotnet restore to flag hallucinated NuGet package names before committing. [src4, src10]
If the team has NO Copilot subscription and wants a free tool
Use the legacy .NET Upgrade Assistant (dotnet tool install -g upgrade-assistant). It receives no updates but is still functional; in VS 2026 enable it under Tools > Options > Projects and Solutions > Modernization. [src4]
If the app must run on Linux containers
Audit for System.Drawing.Common (replace with SkiaSharp or ImageSharp) and any Windows-only Compatibility Pack APIs before retargeting. Test on the target Linux distro -- many APIs throw PlatformNotSupportedException at runtime, not at build. [src1]
Step-by-Step Guide
1. Assess dependencies and compatibility
Run the GitHub Copilot app modernization agent (VS 2026) or the legacy .NET Upgrade Assistant analysis to identify incompatible APIs, unsupported technologies, and third-party library gaps. [src1, src4]
# Option A: Legacy CLI (deprecated but functional)
dotnet tool install -g upgrade-assistant
upgrade-assistant analyze <MySolution.sln>
# Option B: In Visual Studio 2026, open Copilot Chat and type:
# "Analyze this solution for migration from .NET Framework to .NET 10"
Verify: Review the generated report. Note any APIs flagged as unsupported.
2. Retarget .NET Framework to at least 4.7.2
Ensure your project targets .NET Framework 4.7.2 or later. This maximizes API overlap with .NET Standard 2.0. [src1]
<!-- Update TargetFrameworkVersion -->
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
Verify: msbuild /t:Restore && msbuild completes with no errors. Run full test suite.
3. Convert to SDK-style project format
Migrate from legacy .csproj format to SDK-style. Required for targeting modern .NET. [src1]
# Use try-convert tool
dotnet tool install -g try-convert
try-convert --project <MyProject.csproj>
Verify: dotnet build succeeds. NuGet packages use PackageReference.
4. Migrate NuGet packages to PackageReference
Convert from packages.config to PackageReference format. [src1]
<!-- In .csproj: replace packages.config with -->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
Verify: dotnet restore succeeds. Delete the old packages.config.
5. Retarget to .NET 10
Change target framework to net10.0. Fix compilation errors from missing APIs. [src1, src8]
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
# Add Windows Compatibility Pack for missing APIs
dotnet add package Microsoft.Windows.Compatibility
Verify: dotnet build succeeds. Run tests. Fix any PlatformNotSupportedException.
6. Migrate ASP.NET-specific code
For ASP.NET MVC/WebAPI, replace System.Web with ASP.NET Core equivalents. For large apps, use incremental YARP proxy approach. [src3, src5]
// BEFORE: using System.Web.Mvc;
// public class HomeController : Controller { public ActionResult Index() ... }
// AFTER: using Microsoft.AspNetCore.Mvc;
// public class HomeController : Controller { public IActionResult Index() ... }
Verify: Routes return the same responses. Compare HTTP status codes and response bodies.
7. Replace unsupported technologies
Migrate WCF, Entity Framework 6, and other unsupported libraries to modern equivalents. [src1, src7]
// WCF -> gRPC (or CoreWCF for direct port)
// EF6 DbContext -> EF Core 10 DbContext with OnConfiguring
Verify: All database queries return correct results. Run integration tests.
Code Examples
C#: Migrating HTTP Module to ASP.NET Core Middleware
// Input: An ASP.NET Framework HTTP module (IHttpModule) for request logging
// Output: Equivalent ASP.NET Core middleware with DI support
// BEFORE: ASP.NET Framework HTTP Module
// public class RequestLoggingModule : IHttpModule
// {
// public void Init(HttpApplication application)
// {
// application.BeginRequest += (s, e) => {
// var context = ((HttpApplication)s).Context;
// Debug.WriteLine($"Request: {context.Request.Url}");
// };
// }
// public void Dispose() { }
// }
// AFTER: ASP.NET Core Middleware
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next,
ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation("Request: {Method} {Path}",
context.Request.Method, context.Request.Path);
await _next(context);
_logger.LogInformation("Response: {StatusCode}",
context.Response.StatusCode);
}
}
// Registration in Program.cs:
// app.UseMiddleware<RequestLoggingMiddleware>();
C#: Migrating Web.config to appsettings.json with Options pattern
// Input: XML Web.config AppSettings + ConfigurationManager usage
// Output: JSON appsettings.json + IOptions<T> injection
// BEFORE: Web.config
// <appSettings>
// <add key="SmtpHost" value="smtp.example.com" />
// <add key="SmtpPort" value="587" />
// </appSettings>
// Usage: ConfigurationManager.AppSettings["SmtpHost"]
// AFTER: appsettings.json
// { "EmailSettings": { "SmtpHost": "smtp.example.com", "SmtpPort": 587 } }
public class EmailSettings
{
public string SmtpHost { get; set; } = "";
public int SmtpPort { get; set; } = 587;
public int MaxRetries { get; set; } = 3;
}
// Registration: builder.Services.Configure<EmailSettings>(
// builder.Configuration.GetSection("EmailSettings"));
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}
public void Send(string to, string subject, string body)
{
using var client = new SmtpClient(_settings.SmtpHost, _settings.SmtpPort);
// send logic with _settings.MaxRetries retry attempts
}
}
C#: Migrating Global.asax to Program.cs (.NET 10 minimal hosting)
// Input: ASP.NET Framework Global.asax with Application_Start
// Output: ASP.NET Core Program.cs with equivalent startup and middleware
// BEFORE: Global.asax.cs
// public class MvcApplication : HttpApplication {
// protected void Application_Start() {
// AreaRegistration.RegisterAllAreas();
// FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// RouteConfig.RegisterRoutes(RouteTable.Routes);
// }
// }
// AFTER: Program.cs (.NET 10)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
});
builder.Services.AddLogging();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Anti-Patterns
Wrong: Using ConfigurationManager in ASP.NET Core
// BAD -- ConfigurationManager does not work with appsettings.json
using System.Configuration;
var host = ConfigurationManager.AppSettings["SmtpHost"]; // Returns null
Correct: Use IConfiguration or IOptions
// GOOD -- Built-in configuration reads appsettings.json
public class EmailService
{
public EmailService(IConfiguration config)
{
var host = config["EmailSettings:SmtpHost"];
}
}
Wrong: Referencing System.Web types in shared libraries
// BAD -- System.Web does not exist in .NET Core
using System.Web;
public string GetClientIp(HttpContext context)
{
return context.Request.UserHostAddress;
}
Correct: Use Microsoft.AspNetCore.Http
// GOOD -- ASP.NET Core HttpContext
using Microsoft.AspNetCore.Http;
public string GetClientIp(HttpContext context)
{
return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}
Wrong: Copying entire .csproj and manually editing
<!-- BAD -- Old-format project files have hundreds of lines -->
<Project ToolsVersion="15.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\..." />
<!-- 200+ lines -->
</Project>
Correct: Use SDK-style project format
<!-- GOOD -- SDK-style: clean, minimal -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
</ItemGroup>
</Project>
Wrong: Big-bang rewrite of the entire application
// BAD -- Rewriting everything at once stalls feature development
// "Freeze features and rewrite the entire 200K LOC app in .NET 10"
// Result: 6+ months delay, regressions, team burnout
Correct: Incremental migration using YARP proxy
// GOOD -- Strangler Fig: migrate one route at a time
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
// Proxy unmigrated routes to legacy .NET Framework app
Wrong: Using BinaryFormatter for serialization
// BAD -- BinaryFormatter fully removed in .NET 10 (disabled .NET 8, removed .NET 9+)
var formatter = new BinaryFormatter();
formatter.Serialize(stream, myObject); // Compile error in .NET 10
Correct: Use System.Text.Json or protobuf
// GOOD -- System.Text.Json is the default serializer
using System.Text.Json;
var json = JsonSerializer.Serialize(myObject);
var obj = JsonSerializer.Deserialize<MyType>(json);
Wrong: Targeting .NET 8 for new migrations in 2026
<!-- BAD -- .NET 8 LTS support ends November 2026 -->
<TargetFramework>net8.0</TargetFramework>
Correct: Target .NET 10 LTS directly
<!-- GOOD -- .NET 10 LTS supported until November 2028 -->
<TargetFramework>net10.0</TargetFramework>
Common Pitfalls
- Unsupported technologies with no migration path: Web Forms, Windows Workflow Foundation, and AppDomains have no direct equivalent in .NET Core. Fix: Rewrite Web Forms as Blazor/Razor Pages; replace WF with Elsa Workflows or CoreWF; use
AssemblyLoadContextinstead of AppDomains. [src1] - Third-party NuGet packages targeting only .NET Framework: Many older packages lack .NET Standard/Core targets. Fix: Check NuGet for updated versions, use
Microsoft.Windows.Compatibility, or find alternative packages. [src1] - EF6 migrations not compatible with EF Core: Database migrations from Entity Framework 6 cannot be used with EF Core. Fix: Create a new initial EF Core migration from existing schema using
dotnet ef dbcontext scaffold. [src2] - System.Drawing.Common is Windows-only in .NET 6+: Throws
PlatformNotSupportedExceptionon Linux/macOS. Fix: Use SkiaSharp or ImageSharp for cross-platform image processing. [src1] - BinaryFormatter fully removed in .NET 10: Was disabled-by-default in .NET 8, completely removed in .NET 9/10. Fix: Switch to
System.Text.Json, MessagePack, or Protocol Buffers. Audit all[Serializable]attributes. [src2, src9] - Session state API differences: ASP.NET Framework and ASP.NET Core have fundamentally different session APIs. Fix: Use System.Web adapters for shared session during incremental migration. [src5]
- DI container mismatch: Apps using Unity, Autofac, or Ninject need to migrate to ASP.NET Core's built-in DI. Fix: Register services in
Program.csusingbuilder.Services.AddScoped<T>(). [src3] - ProcessStartInfo.UseShellExecute default changed: Default is
falsein .NET Core (wastruein .NET Framework). Fix: Explicitly setUseShellExecute = trueif your code relies on shell execution. [src2] - GitHub Copilot app modernization suggesting non-existent packages: The AI-based tool occasionally suggests NuGet packages that don't exist. Fix: Always verify generated
PackageReferenceentries withdotnet restoreand cross-check on nuget.org. [src4]
Diagnostic Commands
# Check installed .NET SDKs and runtimes
dotnet --list-sdks
dotnet --list-runtimes
# Analyze a project for migration issues (legacy CLI, deprecated)
dotnet tool install -g upgrade-assistant
upgrade-assistant analyze <MySolution.sln>
# Check API compatibility against .NET 10
dotnet tool install -g apicompat
apicompat -a <MyAssembly.dll> -f net10.0
# List NuGet packages and check for updates
dotnet list package --outdated
dotnet list package --vulnerable
# Convert old project format to SDK-style
dotnet tool install -g try-convert
try-convert --project <MyProject.csproj>
# Scaffold EF Core context from existing database
dotnet ef dbcontext scaffold "Server=.;Database=MyDb;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models
# Verify target framework and SDK version
dotnet --info
Version History & Compatibility
| Version | Status | Release | Key Migration Notes |
|---|---|---|---|
| .NET 10 | LTS (Current) | Nov 2025 | Recommended target. LTS until Nov 10, 2028. C# 14, AI integration, NativeAOT, post-quantum crypto, passkey auth. Latest servicing: 10.0.8 (May 12, 2026). |
| .NET 9 | STS (EOL Nov 10, 2026) | Nov 2024 | After Microsoft's STS extension from 18 to 24 months, .NET 9 support now runs until Nov 10, 2026 -- the same day .NET 8 LTS expires. AOT improvements, new LINQ methods. Migrate to .NET 10 before Nov 2026. |
| .NET 8 | LTS (EOL Nov 10, 2026) | Nov 2023 | LTS until Nov 10, 2026. Blazor SSR, Native AOT for ASP.NET Core. After May 2026, only security fixes ship -- plan upgrade to .NET 10 now. |
| .NET 7 | EOL (May 2024) | Nov 2022 | Skip -- go directly to .NET 10. |
| .NET 6 | EOL (Nov 2024) | Nov 2021 | Minimal hosting introduced. Was LTS. |
| .NET 5 | EOL (May 2022) | Nov 2020 | First unified .NET brand. Skip. |
| .NET Core 3.1 | EOL (Dec 2022) | Dec 2019 | Last "Core" version. WinForms/WPF added. |
| .NET Framework 4.8.1 | Maintained | Aug 2022 | Last .NET Framework release. Security patches only. |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Starting any new .NET project | App exclusively uses Web Forms with no rewrite budget | Stay on .NET Framework 4.8 |
| App needs cross-platform (Linux containers) | Small internal tool working fine on .NET Framework | Maintain as-is until EOL pressure |
| Performance is critical (Kestrel is faster than IIS+System.Web) | Heavy COM+ / EnterpriseServices dependency | Stay on .NET Framework or refactor first |
| Team wants modern C# features (records, primary constructors) | Massive WCF server with hundreds of contracts | Port to CoreWCF first |
| Hosting costs matter (Linux is cheaper) | All third-party deps target only .NET Framework | Wait for package updates |
| Need Native AOT or container deployment | Project in maintenance mode, no active development | Leave on .NET Framework |
| Need AI integration (Microsoft.Extensions.AI, MCP support) | Migration budget exhausted and .NET Framework still patched | Defer migration |
Important Caveats
- .NET Framework 4.8.1 is the last .NET Framework release. It will receive security patches through the Windows support lifecycle but no new features. Migration is inevitable for long-lived applications. [src1]
- The .NET Upgrade Assistant CLI is officially deprecated as of 2025. Microsoft recommends the GitHub Copilot app modernization agent (the
modernize-dotnetCopilot agent) as the primary migration tool. As of March 12, 2026 the same workflow runs across Visual Studio, VS Code, GitHub Copilot CLI, and GitHub.com -- not just Visual Studio. The legacy CLI is still installable (and can be re-enabled in VS 2026 via Tools > Options > Projects and Solutions > Modernization) but receives no updates. Unlike the free Upgrade Assistant, Copilot modernization requires a paid Copilot SKU (Pro, Pro+, Business, or Enterprise). [src4, src10] Microsoft.Windows.CompatibilityNuGet package provides many .NET Framework APIs on .NET Core, but some are Windows-only stubs that throwPlatformNotSupportedExceptionon Linux/macOS. [src1]- Code Access Security (CAS) and Security Transparency are not supported in .NET Core. Use OS-level security boundaries instead. [src1]
- Incremental migration with YARP requires running both apps simultaneously, increasing infrastructure complexity. [src5]
- Multi-targeting (
<TargetFrameworks>net472;net10.0</TargetFrameworks>) doubles build and test time but enables gradual migration. [src7] - .NET 10 requires Visual Studio 2022 version 17.12+ or Visual Studio 2026. Older VS versions cannot target .NET 10. [src8]
- .NET 10 includes post-quantum cryptography support (ML-KEM) and first-class MCP (Model Context Protocol) support for AI agent integration. [src8]