.NET Framework to .NET 10 Migration Guide

How to migrate from .NET Framework to .NET 10 LTS — breaking changes, EF Core 10, middleware pipeline, authentication, and phased delivery. Updated August 2026.

ASP.NET Core Modernization

.NET Framework to .NET 10 Migration Guide

  • Sunday, August 9, 2026

How to migrate from .NET Framework to .NET 10 LTS — breaking changes, EF Core 10, middleware pipeline, authentication, and phased delivery. Updated August 2026.

If your application is running on .NET Framework and you haven't started planning a migration yet, the timeline just became urgent. Both .NET 8 and .NET 9 reach end of support on November 10, 2026 after that date, Microsoft will no longer provide servicing updates, security fixes, or technical support for these versions. That gives you approximately three months from the time this article was published.

.NET 10 is an LTS release supported until November 10, 2028, and Microsoft strongly recommends that production applications upgrade to .NET 10 to take advantage of the extended support window, significant performance improvements, and new capabilities. It is the correct migration target not .NET 8, which reaches EOL in the same November 2026 window, and not .NET 9, which is STS and EOL on the same date.

This guide covers what the .NET Upgrade Assistant doesn't the strategy, the breaking change categories that require real decisions, the EF Core 10 migration path, the middleware and authentication changes, and how to structure a migration that keeps a production system running throughout.

.NET 10 vs .NET 8 Why Migrate Directly to .NET 10

The previous LTS release .NET 8 reaches end of support on November 10, 2026. Whether you played it safe with .NET 8 LTS or chased the latest features with .NET 9, you now face the same migration deadline. This is not a typo. Migrating from .NET Framework to .NET 8 today means immediately facing another migration to .NET 10 within months. Migrating directly to .NET 10 gives you three years of LTS support through November 2028 the longest supported window currently available in the .NET ecosystem.

.NET 10 is an LTS release recommended for enterprise and production use, with Microsoft specifically advising that production applications upgrade to benefit from its extended support window, performance gains, and new capabilities.

What's meaningfully different in .NET 10 vs .NET 8 for a migration:

  • Native AOT with Entity Framework Core 10 EF Core 10 adds Native AOT compilation support, enabling ahead-of-time compiled applications with significantly faster startup times. This matters most for Azure Functions and container-based deployments.
  • C# 14 new language features including first-class span support, improved pattern matching, and extension members. All C# 13 code is valid C# 14 no breaking changes at the language level.
  • ASP.NET Core 10 improvements minimal API improvements, OpenAPI 3.1 built-in support via Microsoft.AspNetCore.OpenApi, and enhanced Blazor capabilities.
  • New .slnx solution format the new XML-based solution format replaces the legacy .sln format. Migration is optional but straightforward: dotnet solution migrate.
  • Performance .NET 10's defining highlights include its focus on raw performance and runtime efficiency. Benchmarks show 15–25% throughput improvements for ASP.NET Core applications vs .NET 8 depending on workload characteristics.

Is Your Application a Good Candidate for In-Place Migration?

Before choosing an approach, honestly assess what you're migrating. Not every .NET Framework application should be migrated to .NET 10 using the same pattern.

Strong migration candidates:

  • ASP.NET MVC 5 applications with a clean service/repository layer
  • Web API 2 applications where the API surface needs to be preserved
  • Applications where the business logic is sound and the primary problem is the technology stack framework EOL, deployment limitations, hiring difficulty

Applications that need a different approach:

ASP.NET Web Forms applications

Web Forms' page lifecycle, postback model, and server controls have no equivalent in ASP.NET Core 10. Migration is effectively a UI rewrite. The Upgrade Assistant handles project file conversion but cannot convert .aspx pages to Razor. These applications are often better migrated to Blazor or Razor Pages with the frontend rebuilt while preserving backend logic.

WCF service applications

WCF is not supported on .NET Core or .NET 10. WCF services need to be migrated to REST APIs, gRPC, or CoreWCF. See our WCF to ASP.NET Core migration guide.

Heavily COM-dependent applications

COM interop works on .NET 10 on Windows but has deployment constraints. Heavy COM dependency needs individual assessment per component.

The Migration Approach Decision

Approach 1: In-Place Migration

Convert the existing project to target .NET 10, fix breaking changes, update packages, resolve incompatibilities within the same codebase. The application runs on .NET Framework until migration is complete, then switches on deployment.

When this works:

  • Smaller to medium applications (under 100K lines of business code)
  • Applications with good test coverage
  • Teams with dedicated time to complete migration in 2–8 weeks
  • Where a brief planned cutover window is acceptable

Approach 2: Strangler Fig Migration

A new ASP.NET Core 10 project runs alongside the existing .NET Framework application. YARP routes traffic to whichever system handles each endpoint. Components migrate incrementally.

When this works:

  • Large applications (100K+ LOC) where a single migration window isn't realistic
  • Applications requiring continuous availability
  • Teams where migration runs in parallel with ongoing feature development

The key constraint: Both applications share a database during migration. Schema changes must be coordinated the .NET Framework application's EF6 and the new application's EF Core 10 cannot both apply migrations to the same database without a coordination strategy. Typical approach: freeze schema changes during the migration window, or route all schema changes through a single migration coordinator.

Phase 1: Assessment and Tool Setup

Run the .NET Upgrade Assistant in analysis mode first:

# Install the latest Upgrade Assistant
dotnet tool install -g upgrade-assistant

# Analyze without applying changes
upgrade-assistant analyze ./YourSolution.sln \
    --target-tfm-support LTS  # targets .NET 10 as LTS

The analysis report identifies:

  • APIs that don't exist in .NET 10
  • Packages with no .NET 10 compatible version
  • Platform-specific code that won't run cross-platform

NuGet package compatibility for .NET 10

Key packages requiring replacement:

<thscope="col">.NET Framework Package <thscope="col">.NET 10 Replacement

EntityFramework (EF 6)Microsoft.EntityFrameworkCore v10
System.Web.MvcMicrosoft.AspNetCore.Mvc
Microsoft.Owin.*ASP.NET Core middleware
System.Web.Http (Web API 2)Microsoft.AspNetCore.Mvc
Microsoft.AspNet.IdentityMicrosoft.AspNetCore.Identity
Swashbuckle (old)Microsoft.AspNetCore.OpenApi (built-in .NET 10)
Newtonsoft.JsonStill works or migrate to System.Text.Json
log4net / NLogStill work or use Microsoft.Extensions.Logging

System.Web dependency assessment System.Web does not exist in .NET 10. Any code referencing System.Web directly (very common in ASP.NET MVC 5 applications) needs rewriting using ASP.NET Core equivalents. The extent of this dependency is the primary driver of migration complexity.

Phase 2: Project File Conversion

Before (.NET Framework):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build"
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System.Web" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Controllers\HomeController.cs" />
    <!-- every .cs file listed explicitly -->
  </ItemGroup>
</Project>

After (SDK-style for .NET 10):

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="10.*" />
  </ItemGroup>
</Project>

The SDK-style format automatically includes all .cs files no explicit <Compile> entries. The Upgrade Assistant handles this conversion, but verify the output before proceeding.

Optional: Migrate to the new .slnx solution format:

dotnet solution migrate

The new .slnx solution format replaces the 2002-era .sln format and can be migrated with one command. Not required for migration but worth doing the .slnx format is cleaner and resolves long-standing merge conflict issues with the legacy format.

Phase 3: EF6 to EF Core 10

EF6 and EF Core 10 share conceptual similarities but have significant API differences. The migration requires code changes throughout the data access layer.

Key Changes Requiring Decisions

No lazy loading by default. EF6 enables lazy loading automatically navigation properties load when accessed. EF Core 10 disables it by default. Code relying on automatic lazy loading throws exceptions or returns null. Your three options:

Enable lazy loading via UseLazyLoadingProxies() (quick fix, not recommended long-term)

Convert to eager loading with .Include() (recommended)

Use explicit loading where needed

Native AOT support in EF Core 10 a new .NET 10 capability. For applications targeting Native AOT compilation, EF Core 10 adds model compilation that eliminates runtime reflection. If you're targeting containerized or serverless deployments, this is worth configuring:

// In your DbContext   enables Native AOT compatibility
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModel : ModelBuildingConventionSet { }

Database.SqlQuery<T> → FromSqlRaw or FromSqlInterpolated:

// EF6
var results = context.Database
    .SqlQuery<ProductDto>("SELECT * FROM Products WHERE Id = @p0", id)
    .ToList();

// EF Core 10
var results = context.Products
    .FromSqlInterpolated($"SELECT * FROM Products WHERE Id = {id}")
    .ToList();

// Or using ExecuteSqlAsync for non-query operations
await context.Database.ExecuteSqlAsync(
    $"UPDATE Products SET Stock = {newStock} WHERE Id = {id}");

Register DbContext correctly in .NET 10:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default")));

// For high-throughput scenarios   DbContext pooling
builder.Services.AddDbContextPool<AppDbContext>(options =>
    options.UseSqlServer(connectionString),
    poolSize: 128);

EF Core 10 migrations from an existing database:

Scaffold models from existing database: dotnet ef dbcontext scaffold "..." Microsoft.EntityFrameworkCore.SqlServer

Create an initial migration representing current state: dotnet ef migrations add InitialCreate

Mark as already applied: dotnet ef database update 0 then insert the migration record manually

From this point, use EF Core migrations normally

Phase 4: The ASP.NET Core 10 Application Pipeline

Global.asax and web.config for application configuration are replaced entirely by Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Service registration
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default")));

// .NET 10: Built-in OpenAPI (replaces Swashbuckle)
builder.Services.AddOpenApi();

// Register all application services
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// Middleware pipeline   order matters
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

// .NET 10: OpenAPI endpoint
app.MapOpenApi();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Replacing Application_Error:

// Global exception handling middleware   replaces Application_Error
app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var feature = context.Features
            .Get<IExceptionHandlerFeature>();
        var exception = feature?.Error;

        _logger.LogError(exception, "Unhandled exception");

        context.Response.StatusCode = 500;
        await context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Title = "An unexpected error occurred",
            Status = 500
        });
    });
});

Phase 5: Authentication

Forms Authentication → ASP.NET Core Cookie Authentication:

builder.Services.AddAuthentication(
    CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";
        options.AccessDeniedPath = "/Account/AccessDenied";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
        options.SlidingExpiration = true;
    });

ASP.NET Identity 2.x → ASP.NET Core Identity 10:

ASP.NET Core Identity 10 is the current version ships with .NET 10. The user data migrates but the password hashing algorithm changed. Handle legacy passwords with a custom hasher:

// Custom password hasher supporting both legacy and new hash formats
public class LegacyPasswordHasher : PasswordHasher<ApplicationUser>
{
    public override PasswordVerificationResult VerifyHashedPassword(
        ApplicationUser user,
        string hashedPassword,
        string providedPassword)
    {
        // Try the new algorithm first
        var result = base.VerifyHashedPassword(
            user, hashedPassword, providedPassword);

        if (result == PasswordVerificationResult.Failed)
        {
            // Fall back to legacy hash verification
            if (VerifyLegacyHash(hashedPassword, providedPassword))
                return PasswordVerificationResult.SuccessRehashNeeded;
        }
        return result;
    }
}

Register the custom hasher:

builder.Services.AddScoped<IPasswordHasher<ApplicationUser>,
    LegacyPasswordHasher>();

SuccessRehashNeeded tells ASP.NET Core Identity to update the stored hash to the new algorithm on the user's next successful login transparently migrating the password store without requiring a password reset.

Phase 6: Configuration

web.config AppSettings → appsettings.json:

{
  "ConnectionStrings": {
    "Default": "Server=...;Database=...;Trusted_Connection=true;"
  },
  "ApiSettings": {
    "BaseUrl": "https://api.example.com",
    "TimeoutSeconds": 30
  },
  "FeatureFlags": {
    "NewCheckout": true,
    "BetaDashboard": false
  }
}

Strongly-typed configuration (recommended over IConfiguration["key"]):

// Option class
public class ApiSettings
{
    public string BaseUrl { get; set; } = default!;
    public int TimeoutSeconds { get; set; } = 30;
}

// Register in Program.cs
builder.Services.Configure<ApiSettings>(
    builder.Configuration.GetSection("ApiSettings"));

// Inject into services
public class ApiClient(IOptions<ApiSettings> settings)
{
    private readonly ApiSettings _settings = settings.Value;
}

Secrets in production: Connection strings and API keys must move to Azure Key Vault or environment variables. This is a .NET 10 best practice enforced by the configuration system:

// Azure Key Vault integration   no credentials required with Managed Identity
builder.Configuration.AddAzureKeyVault(
    new Uri("https://your-vault.vault.azure.net/"),
    new DefaultAzureCredential());

Phase 7: Dependency Injection

.NET Framework MVC used third-party containers (Unity, Autofac, Ninject). ASP.NET Core 10 has first-class built-in DI:

// Lifetime registrations in Program.cs
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddTransient<IEmailService, SendGridEmailService>();

If your DI configuration is complex and uses Autofac-specific features (decorators, scanning, named registrations), Autofac has a .NET 10 integration package that lets you keep your existing container configuration while using ASP.NET Core's hosting model. This reduces migration scope significantly for applications with extensive DI configuration.

Phase 8: HttpContext and Session

HttpContext.Current → IHttpContextAccessor:

// Register in Program.cs
builder.Services.AddHttpContextAccessor();

// Inject where needed   not everywhere
public class UserContextService(IHttpContextAccessor accessor)
{
    public string? GetCurrentUserId()
        => accessor.HttpContext?
            .User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}

Use IHttpContextAccessor only where genuinely necessary prefer passing user identity as method parameters where possible.

Session in ASP.NET Core 10:

// Registration
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

app.UseSession(); // before UseRouting

// Usage
HttpContext.Session.SetString("CartId", cartId.ToString());
var cartId = HttpContext.Session.GetString("CartId");

For multi-server deployments on Azure App Service, replace AddDistributedMemoryCache with Redis:

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration =
        builder.Configuration["Redis:ConnectionString"];
});

Phase 9: Static Files

BundleConfig.cs and System.Web.Optimization are replaced by ASP.NET Core 10's static file middleware:

app.UseStaticFiles(); // serves files from wwwroot/

Move files: ~/Content/ → wwwroot/css/, ~/Scripts/ → wwwroot/js/, ~/Images/ → wwwroot/images/.

For bundling and minification in .NET 10, two options:

WebOptimizer (server-side, minimal config):

builder.Services.AddWebOptimizer(pipeline =>
{
    pipeline.AddCssBundle("/css/bundle.css",
        "css/site.css", "css/theme.css");
    pipeline.AddJavaScriptBundle("/js/bundle.js",
        "js/app.js", "js/utils.js");
});

Build-time tools (Vite, Webpack, esbuild) preferred for applications with React, Angular, or Vue frontends.

Testing Strategy

Establish HTTP-level integration tests before migration begins they test behavior rather than implementation, so they survive the migration:

public class ProductControllerTests
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ProductControllerTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                services.RemoveAll<AppDbContext>();
                services.AddDbContext<AppDbContext>(options =>
                    options.UseInMemoryDatabase("TestDb"));
            });
        }).CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsOk()
    {
        var response = await _client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();
    }
}

Frequently Asked Questions

Why migrate to .NET 10 instead of .NET 8?

Both .NET 8 and .NET 9 will reach end of support on November 10, 2026. Migrating to .NET 8 today means immediately planning another migration before the end of this year. .NET 10 is the current LTS release with support through November 2028 migrating directly to .NET 10 gives you three years of supported life without an intermediate migration step.

How long does a .NET Framework to .NET 10 migration take?

For a clean ASP.NET MVC 5 application with a service layer: a small application (under 30K LOC) typically takes 3–6 weeks. A medium application (30K–100K LOC) typically takes 6–16 weeks. A large enterprise application using the Strangler Fig pattern typically takes 4–12 months. The biggest time variables are extent of System.Web usage, EF6 lazy loading dependency, and available test coverage.

Can we migrate one project at a time within a large solution?

Yes SDK-style project files support multi-targeting, allowing a class library to target both .NET Framework 4.8 and net10.0 simultaneously during the transition:

<TargetFrameworks>net48;net10.0</TargetFrameworks>

This allows the web project to migrate while shared libraries support both old and new web projects.

What happens to EF6 migrations when migrating to EF Core 10?

Existing EF6 migrations cannot be used with EF Core 10 EF Core has its own migration system and history table. The typical approach: scaffold models from the existing database, create an initial EF Core migration representing the current schema, mark it as already applied, then use EF Core migrations normally from that point forward.

Should we enable Native AOT in .NET 10?

For most migrating ASP.NET Core applications, no not immediately. Native AOT compilation in EF Core 10 is a significant capability but adds constraints (no runtime reflection, no dynamic code generation) that require code changes beyond the standard migration. Consider it as a separate optimization phase after the migration is complete and stable, specifically for containerized or serverless deployment scenarios where startup time is critical.

Is our .NET Framework application at security risk if we don't migrate before November 2026?

Yes. After November 10, 2026, Microsoft will no longer provide servicing updates, security fixes, or technical support for .NET 8 and .NET 9 and by extension the framework versions below them. Running an unpatched web framework exposes the application to known vulnerabilities without remediation. For applications handling sensitive data financial, healthcare, personal this is a compliance risk as well as a security risk.

Closing

The November 2026 EOL deadline for .NET 8 and .NET 9 makes .NET Framework to .NET 10 migration a near-term business requirement rather than a medium-term aspiration. The migration is well-understood engineering work the tooling is mature, the breaking changes are documented, and the end result (current security patches, significantly better performance, cross-platform deployment, AI-ready infrastructure with Microsoft.Extensions.AI and Microsoft Agent Framework) is consistently worth the investment.

If you're planning a .NET Framework to .NET 10 migration and want an experienced team to assess scope and lead the work, our software modernization team has delivered this migration across a range of codebase sizes from focused 4-week migrations to multi-phase enterprise projects running alongside normal development.