Skip to main content

Legacy .NET to Modern .NET Migration Guide

A practical, step-by-step guide to migrating from .NET Framework 4.x to modern .NET -- covering WebForms, MVC 5, WCF, Windows Services, and everything in between.

The .NET Landscape in 2026

Microsoft has drawn a clear line in the sand. .NET Framework 4.8.x is the final version -- it will receive security patches but zero new features. Meanwhile, modern .NET (the unified .NET 5+ lineage) gets annual releases, performance improvements, and the full attention of the ecosystem.

If your organization is still running .NET Framework applications, you are not alone -- but you are on borrowed time. Every month you delay makes the migration harder as the gap between legacy and modern widens and NuGet package authors drop .NET Framework support.

.NET Framework 4.8

Final version. Security patches only. No new features, no performance work, no new APIs. Effectively in maintenance mode since 2019.

.NET 8 (LTS)

Long-term support until November 2026. The safe migration target for most teams today. Mature ecosystem, proven in production.

.NET 9 (STS)

Standard-term support (18 months). Good for greenfield projects. Includes AOT improvements, performance gains, and new runtime features.

.NET 10 (LTS)

Expected November 2025. Next long-term support release. If you are starting a migration now, target .NET 10 for maximum support runway.

Key decision: If you are starting a new migration in 2026, target .NET 8 for stability or .NET 10 for the longest support window. Avoid .NET 9 for migration targets since its shorter support cycle means you would need to upgrade again sooner.

Assessment Checklist: What Are You Migrating From?

Before choosing a migration strategy, you need to know exactly what you are working with. Each legacy technology has a different migration path, different pain points, and different timelines.

ASP.NET WebForms

Hardest Migration
  • .aspx files with server controls
  • ViewState and postback model
  • Code-behind files (.aspx.cs)
  • UpdatePanel for "AJAX"
  • Master pages and user controls

Difficulty: No direct upgrade path. Requires a rewrite using Blazor or Razor Pages.

ASP.NET MVC 5

Most Direct Path
  • Controllers, Views, Models pattern
  • Razor views (.cshtml)
  • Web API controllers
  • Attribute routing
  • Dependency injection (often third-party)

Difficulty: Moderate. Controllers and views translate closely to ASP.NET Core MVC.

WCF Services

Significant Rework
  • Service contracts and data contracts
  • SOAP/XML bindings
  • .svc files and service host
  • Duplex/callback contracts
  • Message-level security

Difficulty: No direct equivalent in modern .NET. Replace with gRPC or minimal APIs.

Windows Services

Clean Replacement
  • ServiceBase class inheritance
  • OnStart/OnStop lifecycle
  • Timer-based background work
  • Windows event log integration
  • SC.exe / installutil deployment

Difficulty: Straightforward. Worker Services are the modern equivalent with cleaner DI and hosting.

Migration Paths by Technology

Each legacy .NET technology has a recommended modern replacement. The migration difficulty varies dramatically -- from near-direct ports (MVC 5) to complete rewrites (WebForms).

WebForms to Blazor or Razor Pages

Recommended approach: Strangler Fig pattern

Why You Cannot Upgrade In-Place

  • WebForms relies on ViewState and the page lifecycle -- concepts that do not exist in modern .NET
  • Server controls (GridView, Repeater, etc.) have no direct equivalents
  • The postback model is fundamentally incompatible with modern request handling
  • Master pages must be rebuilt as layouts

The Strangler Fig Approach

  • Run the legacy WebForms app alongside a new Blazor/Razor Pages app behind a reverse proxy
  • Migrate one page or feature at a time, routing new traffic to the modern app
  • Share authentication via a common identity provider (Azure AD, IdentityServer)
  • Decommission WebForms pages as their replacements are production-ready
WebForms (.aspx) Blazor (interactive UI) or Razor Pages (content/forms)

ASP.NET MVC 5 to ASP.NET Core MVC

The most direct migration path in the .NET ecosystem

What Translates Directly

  • Controllers and action methods (minor signature changes)
  • Razor views (.cshtml) -- mostly copy-paste with namespace updates
  • Model binding and validation attributes
  • Attribute routing
  • Filters (action filters, exception filters)

What Changes

  • Global.asax replaced by Program.cs + middleware pipeline
  • web.config replaced by appsettings.json
  • Third-party DI (Ninject, Autofac) replaced by built-in DI
  • HttpContext.Current replaced by injected IHttpContextAccessor
  • Bundling changes (BundleConfig to webpack, Vite, or LibMan)

Pro tip: Microsoft provides the official migration documentation and the .NET Upgrade Assistant tool that automates many of the mechanical changes. Start there and handle the manual work afterward.

WCF to gRPC or Minimal APIs

Choose based on whether clients are internal or external

gRPC (Internal Services)

Best replacement for service-to-service WCF calls. Binary protocol, code-gen from .proto files, streaming support.

  • Strongly typed contracts (like WCF)
  • Bi-directional streaming (replaces duplex)
  • 5-10x faster than SOAP/XML
  • Not browser-friendly without gRPC-Web

Minimal APIs / REST (External)

Best for WCF services consumed by browsers, mobile apps, or third parties. JSON over HTTP/HTTPS.

  • Universal client compatibility
  • OpenAPI/Swagger documentation
  • Simpler debugging and testing
  • No built-in streaming contracts

CoreWCF note: The community-maintained CoreWCF project provides a compatibility layer for running WCF services on modern .NET. It is a viable stopgap for migrating the hosting model while you plan the full API redesign, but it is not a long-term solution.

Windows Services to Worker Services

Cleanest migration path -- modern .NET Worker Services are a direct upgrade

Remove

  • ServiceBase inheritance
  • OnStart/OnStop overrides
  • ProjectInstaller class
  • installutil.exe deployment

Add

  • BackgroundService base class
  • ExecuteAsync method
  • Built-in DI container
  • IHostedService for multiple workers

Bonus

  • Runs on Linux (containerizable)
  • Health checks built-in
  • Graceful shutdown handling
  • Can still install as Windows Service

Entity Framework 6 to EF Core

Data access migration -- often the biggest hidden effort

What Carries Over

  • DbContext pattern (with namespace changes)
  • Code-first migrations (recreate migration history)
  • LINQ queries (mostly compatible)
  • Data annotations for validation

Breaking Changes to Watch

  • Lazy loading is opt-in (not default)
  • ObjectContext is gone -- DbContext only
  • Complex type mapping differences
  • EDMX (database-first visual designer) not supported

Migration strategy: If you use database-first with EDMX, scaffold your models using dotnet ef dbcontext scaffold to generate code-first models from your existing database. Then compare with your EF6 models and reconcile differences.

10-Step Migration Process

Regardless of which legacy technology you are migrating, this process gives you a repeatable, low-risk path from old to new.

1

Inventory and Dependency Map

Catalog every project in your solution. Map dependencies between them. Identify NuGet packages, third-party DLLs, COM references, and system dependencies. You cannot migrate what you do not understand.

2

Run the .NET Upgrade Assistant

Microsoft's free tool analyzes your project and automates mechanical changes: project file conversion, namespace updates, NuGet package replacements. It handles the tedious work so you can focus on the architectural decisions.

3

Migrate Shared Libraries First

Convert your lowest-level class libraries to .NET Standard 2.0 or multi-target. This lets them be consumed by both old and new projects during the transition, enabling incremental migration instead of a big-bang switch.

4

Replace web.config with appsettings.json

Extract connection strings, app settings, and custom configuration sections into the new configuration system. Use the Options pattern for strongly-typed settings. Secrets go to User Secrets or Azure Key Vault -- not into JSON files.

5

Migrate Authentication

This is often the most complex step. Forms Authentication and Windows Authentication need to be replaced with ASP.NET Core Identity, OAuth 2.0/OIDC, or Azure AD. Plan for a shared auth mechanism during the transition period.

6

Port the Data Access Layer

Migrate from EF6 to EF Core or from raw ADO.NET to Dapper/EF Core. Rebuild your migrations from the current database schema. Test every query -- EF Core generates different SQL than EF6, and subtle behavior differences can cause data bugs.

7

Rebuild the Middleware Pipeline

Replace HTTP modules and handlers with ASP.NET Core middleware. The pipeline order matters -- authentication before authorization, error handling at the top, static files early. Map your old Global.asax events to their middleware equivalents.

8

Parallel Testing with Feature Flags

Run the old and new systems in parallel. Use feature flags to route a percentage of traffic to the new system. Compare responses, measure performance, and catch regressions before you cut over completely.

9

Cutover and Decommission

Once the new system passes all tests and handles production traffic cleanly, switch 100% of traffic. Keep the old system running in read-only mode for 2-4 weeks as a safety net. Then decommission it -- do not leave zombie legacy apps running.

10

Post-Migration Optimization

A straight port is not the finish line. Once you are on modern .NET, take advantage of features you could not use before: native AOT compilation, minimal APIs for simple endpoints, source generators for performance, and the latest C# language features.

NuGet Package Compatibility

NuGet packages are the number one source of unexpected migration blockers. A package that works on .NET Framework may not have a modern .NET version -- or may have breaking API changes in the version that does.

Common Package Migration Issues

Legacy PackageModern ReplacementMigration Notes
Newtonsoft.JsonSystem.Text.JsonBuilt-in, faster, but less feature-rich. Newtonsoft still works on modern .NET if you need it.
log4net / NLogSerilog or Microsoft.Extensions.LoggingBuilt-in logging abstractions with structured logging. NLog/Serilog both support modern .NET.
Ninject / UnityBuilt-in DIASP.NET Core has built-in DI. Autofac and Scrutor add advanced features if needed.
AutoMapperMapster or manual mappingAutoMapper works on modern .NET but has licensing changes. Mapster is faster and free.
System.WebMicrosoft.AspNetCore.*The biggest breaking change. Everything in System.Web is replaced by ASP.NET Core equivalents.
System.DrawingSkiaSharp or ImageSharpSystem.Drawing is Windows-only. Cross-platform alternatives are required for Linux/container deployments.

Check compatibility early: Run dotnet tool install --global apicompat or use the .NET Upgrade Assistant to scan your packages before you start. Discovering an incompatible package mid-migration is painful -- discovering it early is a planning exercise.

Configuration Migration: web.config to appsettings.json

The .NET Framework configuration system (XML-based web.config with ConfigurationManager) is replaced by a layered JSON-based system. This is more flexible but requires rethinking how you structure settings.

Old: web.config

  • <appSettings> for key-value pairs
  • <connectionStrings> section
  • Custom configuration sections (IConfigurationSectionHandler)
  • ConfigurationManager.AppSettings["key"]
  • Transform files (web.Debug.config, web.Release.config)

New: appsettings.json

  • Hierarchical JSON structure (nested objects)
  • ConnectionStrings section in JSON
  • Options pattern with POCO classes (IOptions<T>)
  • IConfiguration injected via DI
  • Environment-specific overrides (appsettings.Production.json)

Do not forget: web.config in .NET Framework also controlled IIS settings (authentication modes, custom errors, URL rewrite rules). In modern .NET, these are handled by middleware configuration in Program.cs or a separate web.config that only configures the ASP.NET Core Module in IIS.

Authentication Migration

Authentication is frequently the hardest part of a .NET migration because it touches every page, every API call, and every user session. Get this wrong and you lock users out -- get it right and nobody notices.

Forms Authentication

ASP.NET Core Identity + Cookie Auth

The closest equivalent is ASP.NET Core Identity with cookie authentication. The membership database schema is different, so plan a one-time user data migration. Password hashes from the old Membership provider are not compatible -- you will need to implement a custom hasher that verifies old hashes and re-hashes on first login.

Transition tip: During migration, share authentication between old and new apps using a common cookie domain and data protection key store.

Windows Authentication

Negotiate/NTLM or Azure AD/Entra ID

For intranet apps, ASP.NET Core still supports Windows Authentication via the Negotiate handler. For apps moving to the cloud or needing cross-platform support, migrate to Azure AD (now Entra ID) with OpenID Connect. This also enables SSO across your application portfolio.

Custom/Homegrown Auth

OAuth 2.0 / OpenID Connect

If you rolled your own authentication (custom token validation, hand-built login pages with manual hash comparison), this migration is your chance to move to standards. Use IdentityServer (Duende), Auth0, or Azure AD B2C. The migration effort is higher, but you eliminate an entire category of security debt.

Testing Strategy During Migration

Migration without a testing strategy is a recipe for production incidents. You need to verify that the new system behaves identically to the old one -- and you need to catch differences before users do.

Characterization Tests

Before touching any code, write tests against the legacy system that capture its current behavior -- including bugs. These tests become your safety net. If the migrated system passes them all, you have behavioral equivalence.

Shadow Testing

Route production traffic to both the old and new systems simultaneously. Compare responses. Log differences. This catches edge cases that unit tests miss -- real users produce inputs that test data never imagines.

Performance Benchmarks

Benchmark the legacy system's response times, throughput, and memory usage before migration. The new system should meet or exceed these numbers. Modern .NET is typically 2-5x faster, but a bad migration can erase those gains.

End-to-End Tests

Use Playwright or Selenium to automate critical user journeys: login, CRUD operations, reporting, checkout flows. These tests should work against both the old and new systems without modification.

Data Integrity Checks

After migrating data access code, run both systems against a copy of the production database. Compare query results row by row. EF Core can generate different SQL than EF6 -- especially for complex joins and grouping operations.

Rollback Testing

Practice the rollback procedure before you need it. Can you switch traffic back to the old system in under 5 minutes? Are database changes backward-compatible? Test this in staging -- not during a production incident at 2 AM.

Related Resources

Frequently Asked Questions

It depends on what you are migrating. An MVC 5 app with clean separation of concerns can often be migrated in 2-4 months. A large WebForms application with deep ViewState dependencies and custom server controls typically takes 6-18 months using the strangler fig approach. Windows Services are usually the fastest at 1-2 weeks per service. The biggest variable is not the technology -- it is the quality of the existing codebase and how tangled the dependencies are.

Skip intermediate versions and go directly to the latest LTS release (.NET 8 or .NET 10). There is no benefit to stepping through .NET Core 3.1, .NET 5, .NET 6, and so on. The .NET Upgrade Assistant is designed to handle the full jump. The only exception is if you have a dependency that only supports a specific intermediate version -- in that case, migrate to that version first, then plan a second hop.

Yes, and you should. The strangler fig pattern depends on running both systems simultaneously. Use a reverse proxy (IIS URL Rewrite, YARP, or nginx) to route requests to the appropriate backend. Share authentication state using a common cookie domain, shared database, or an external identity provider. Shared libraries should target .NET Standard 2.0 so both runtimes can consume them.

WinForms and WPF are both supported on modern .NET (they shipped with .NET Core 3.0+), so you can migrate them without switching UI frameworks. The .NET Upgrade Assistant handles most of the project file and namespace changes. .NET MAUI is only worth considering if you need cross-platform (iOS, Android, macOS) support -- it is not a direct replacement for WinForms or WPF, and the migration effort is significantly higher.

Frame it as risk mitigation and cost savings, not technical improvement. Calculate the cost of running on unsupported software (no security patches, increasing CVEs, compliance risk). Show the developer productivity tax (time spent on workarounds, inability to use modern NuGet packages, difficulty hiring developers who want to work on legacy .NET). Quantify the performance gains -- modern .NET is 2-5x faster, which translates directly to infrastructure cost savings. See our Selling to Management guide for a full framework.

Underestimating the scope. The code migration is the visible part, but the hidden work includes: configuration migration, authentication rework, CI/CD pipeline changes, deployment infrastructure updates (IIS configuration, container setup), monitoring and logging reconfiguration, and team training. The code port might be 40% of the total effort -- the remaining 60% is everything around it. Budget for the full picture, not just the code changes.

Ready to Start Your .NET Migration?

Migration is a journey, not a weekend project. Start with an assessment, build your business case, and execute incrementally.