If your organisation is running production workloads on .NET Framework 4.x, you’re not alone. Millions of line-of-business applications, internal tools, and customer-facing platforms still run on a framework that Microsoft has placed into maintenance mode. It works. It’s stable. And it’s quietly becoming a liability.
The case for modernisation isn’t about chasing the latest technology. It’s about performance, security, talent, and cost. Modern .NET (8 and beyond) offers dramatically better runtime performance, native container support, cross-platform deployment, and access to the latest language features and libraries. Equally importantly, the talent pool is shifting. Experienced .NET developers increasingly want to work with modern tooling, and recruiting for legacy Framework projects is becoming harder and more expensive every year.
This post is a practical playbook for migrating from .NET Framework to .NET 8+. It’s drawn from real migration projects we’ve delivered, including large-scale enterprise systems with millions of daily transactions. The patterns here are battle-tested.
Assess Before You Migrate
The biggest mistake organisations make is treating migration as a purely technical exercise. Before writing any code, you need a clear picture of what you have, what depends on it, and what the business actually needs from the migrated system.
Start with a dependency audit. Map every NuGet package, COM component, Windows-specific API call, and third-party integration in your solution. Tools like the .NET Upgrade Assistant and try-convert can automate some of this, but manual review is essential for anything beyond trivial applications. Pay particular attention to System.Web dependencies, WCF service references, and any use of the Global Assembly Cache.
Next, classify your applications by migration complexity. Simple class libraries and console applications often migrate with minimal changes. ASP.NET Web API projects require moderate effort to move to ASP.NET Core. ASP.NET MVC applications with heavy use of System.Web are the most complex. And anything using WCF on the server side will need a strategy for replacement, whether that’s gRPC, CoreWCF, or a REST-based alternative.
Finally, establish your success criteria. What does “done” look like? Is it feature parity, or is this also an opportunity to retire unused functionality? Define the measurable outcomes – performance benchmarks, deployment targets, security requirements – before you start.
The Strangler Fig Pattern
For non-trivial systems, the strangler fig pattern is the safest and most practical migration approach. Named after the tropical fig that gradually envelops a host tree, the pattern works by building new functionality on the modern stack while gradually migrating existing features, all behind a single entry point.
In practice, this means placing a reverse proxy or API gateway in front of your legacy application. New endpoints are built in .NET 8+ and routed through the gateway. Existing endpoints continue to hit the legacy system. Over time, as features are migrated, traffic shifts from legacy to modern until the old system can be decommissioned entirely.
The critical advantage of this approach is that the business never stops. There’s no big-bang cutover, no feature freeze, and no weekend migration prayer. Users experience a gradual improvement rather than a risky switch. At any point, you can pause the migration without leaving the system in a broken state.
The strangler fig pattern turns a high-risk big-bang migration into a series of low-risk incremental changes. Each step is independently deployable and reversible.
API-First Decomposition
One of the most valuable side effects of a .NET modernisation project is the opportunity to improve the system’s architecture along the way. Legacy .NET Framework applications are frequently monolithic, with tightly coupled layers, shared database access, and business logic embedded in controllers or code-behind files.
As you migrate, decompose the system around clear API boundaries. Each migrated component should expose a well-defined contract – ideally documented with OpenAPI – and communicate with other components through those contracts rather than through shared databases or in-process calls.
This doesn’t mean you need to adopt microservices. For most organisations, a well-structured modular monolith on .NET 8+ is the right target architecture. The key is clean separation of concerns: each module owns its data, exposes its functionality through APIs, and can be independently tested and deployed. If you need to extract a module into a separate service later, the API boundary is already defined.
Use MediatR or a similar mediator pattern internally to decouple request handling from business logic. This makes it straightforward to migrate individual features without touching the surrounding code.
Containerisation Strategy
Modern .NET is built for containers, and containerisation should be part of your migration strategy from the start. Even if your current deployment target is Windows Server, moving to containers unlocks consistent environments across development and production, simplified scaling and orchestration with Kubernetes or AWS ECS, blue-green and canary deployment patterns, and infrastructure as code for repeatable, auditable deployments.
Start by containerising the new .NET 8+ components as they’re built. Use multi-stage Docker builds to keep images lean, and establish a CI/CD pipeline that builds, tests, and publishes container images automatically. If your legacy application must coexist during the migration, Windows containers can host .NET Framework components alongside Linux containers for the modern stack, though we generally recommend minimising this hybrid state.
For AWS environments, ECS with Fargate provides a managed container platform that eliminates the need to manage underlying infrastructure. Combined with Application Load Balancer, this gives you the routing layer needed for the strangler fig pattern.
Keeping the Business Running
The technical patterns matter, but the success of a migration ultimately depends on how well you manage the non-technical aspects. This means maintaining feature delivery throughout the migration. If the business has to choose between new features and modernisation, modernisation will always lose. Structure the work so that both can happen in parallel.
It also means investing in automated testing before you start migrating. If you don’t have a solid test suite for the legacy system, build one – at least at the integration and API level. These tests become your safety net during migration, confirming that migrated components behave identically to their predecessors.
Communication with stakeholders is equally important. Set expectations that migration is a continuous process, not a project with a fixed end date. Report progress in terms the business understands: features migrated, performance improvements measured, risks retired. Avoid framing it purely as a technical initiative.
Finally, plan for data migration carefully. If the modernised system uses a different database schema, you’ll need a strategy for keeping data in sync during the transition period. Event sourcing, change data capture, or dual-write patterns can all work, depending on your consistency requirements.
A Realistic Timeline
Every migration is different, but as a rough guide: a well-scoped migration of a medium-complexity .NET Framework application (50–100k lines of code, moderate dependency complexity) typically takes 3–6 months using the strangler fig approach, with the first migrated components in production within 4–6 weeks.
The most important thing is to start. Every month spent on .NET Framework is another month of accumulating risk – security patches that won’t come, libraries that won’t update, and developers who’d rather work elsewhere. The migration doesn’t have to happen all at once, but it does have to start.