.NET Framework Application Modernization

There’s more to .NET Framework modernization than porting code
You can view most applications through different lenses: the source code and runtime itself, the logical and system architecture, and the infrastructure it runs on. There are also cross-cutting concerns including observability, the build/test/release process, and the structure of your teams.
We recommend a holistic and iterative approach to modernizing these applications after a thorough assessment of their existing codebase and its dependencies and in alignment with your business and technical goals. A holistic approach modernizes across all the lenses and takes into account your team’s skills and tools. An iterative approach means progressively modernizing slices of functionality rather than a “big bang” approach which often leads to failure.
A Brief History of .NET
The Windows-only Era
Microsoft released .NET Framework 1.0 in 2002 as an application framework for Windows that included a common-language runtime, base class libraries, and support for multiple languages including VB.NET and C#. Additional versions were released over time, with 4.8.1 being the final release in 2022, which added support for arm64 processors. The first web application project type for .NET Framework was ASP.NET Web Forms in which HTML, CSS, JavaScript and server-side code are all intermingled on ASPX pages (which have the .aspx extension) and ASCX user controls. Later, Microsoft introduced the MVC project type (Model-View-Controller), and Web API (for REST APIs). The default C# version for .NET Framework is version 7.3, and many new language features in later C# versions are not compatible with .NET Framework (C# version 14 launches in 2025).
A new generation of .NET
In 2016, a new cross-platform, multi-architecture and open-source implementation of .NET was released by the .NET Foundation as .NET Core 1.0. Versions 2.x and 3.x quickly followed. After .NET Core 3.1, the platform was renamed to simply “.NET”, and the next version released was .NET 5. There is no .NET Core 4.x or .NET 4.x to avoid confusion with .NET Framework 4.x. Web applications built on .NET primarily use ASP.NET Core, which supports native project types for MVC, Web API, Razor Pages, Blazor / Blazor WebAssembly, and Worker Service. .NET Core 2.1 introduced support for the arm64 architecture, but .NET 5.0 introduced numerous optimizations that made it a compelling choice for cloud workloads.
Modernization takes off
Starting circa 2022, after the November 2021 release of .NET 6, an increasing number of organizations began modernization projects to convert their legacy Windows-bound .NET Framework applications to .NET 6 or higher, often with the goal of moving to Linux, containers and serverless, and on AWS Graviton arm64 processors when feasible.
.NET Framework Application Types and Modernization Level of Effort
There are a variety of application types built on .NET Framework that run in the AWS cloud. The most common we see are ASP.NET web applications, although most companies also run a variety of other application types. The level of effort to move to cross-platform versions of .NET to run on Linux varies tremendously by type. It’s also important to assess applications for Windows OS dependencies such COM objects, use of DLL imports, and NuGet packages that are Windows-specific as these will increase the effort to migrate from Windows to Linux.
1 Windows Communication Foundation
2 While Blazor is the current recommendation, an emerging possibility for Web Forms is to port back-end code to .NET 8+ and leverage CoreWebForms , an open-source implementation of parts of the System.Web namespace, to run web forms and user controls in ASP.NETÂ Core.
3 ASP.NET Web Forms applications can run on Linux using the open-source Mono implementation of the .NET runtime. However, this generally requires refactoring, and introduces troubleshooting challenges. This modernization path is not recommended for applications being actively developed.
Monoliths to Distributed Services

Monoliths were the default for decades
Prior to the early 2010s, most software applications were monoliths: they were deployed as a single unit, and the entire application ran in a single process. While most .NET Framework applications has multiple projects, such as a data-access layer project, a business logic layer, a web or fat-client UI, etc., the calls between layers were in memory, and code changes in any layer could change – or break – functionality in other layers. These .NET Framework monoliths are typically deployed on one or more Windows servers. This design means that if you need to scale out one small bit of functionality, you scale out the entire application by launching more Windows servers (or VMs), which is slow and inefficient. Monoliths typically share one or more databases, with different components using the same tables, which makes schema updates painful as multiple layers and components are affected. And if there are multiple teams working on different components, they must all communicate and orchestrate changes and deployments, which can result in infrequent feature releases and slow delivery.
Distributed services take off
Distributed services – including microservices – break up this design into multiple smaller applications (“services”) which can communicate with each other. Each service can be deployed and scaled on its own, and the smaller size of each makes them ideal candidates for containers and serverless. Microservices also don’t (or shouldn’t) all share the same database either – ideally they each have their own data store, and if service A needs data from service B, it requests it via service B’s API. Starting in the mid-2010s, microservices became the default architectural pattern at many large tech companies as they hit the limits of monolithic applications.
Monoliths are not inherently bad – for medium sized applications that don’t require large teams and don’t need to rapidly scale, a monolith can work fine. But for large applications that need dynamic and granular scaling, that have large teams, or that require fast feature delivery and automated deployments, decomposing monoliths into smaller distributed services is often essential. A common pattern for tackling ASP.NET monoliths is progressively carving off vertical slices of functionality, potentially one or a small group of related controllers or pages that handle the same domain objects, into its own service. Ideally this means extracting out or isolating the database schema the service interacts with, and refactoring the remaining monolithic code to call the new service when it needs access to that data. The first few services carved off are the hardest – this work becomes progressively easier as the monolith shrinks.
Defining service boundaries
Choosing where to “cut” or extract out functionality can be challenging. Tools like the AWS Microservice Extractor for .NET, CAST Imaging and NDepend are useful to visualize dependencies between functional units in source code. At Evolve Cloud Services we leverage these as well as Visual Studio’s Code Map to map relationships in code. Running an event-modeling workshop with your technical team and product owners can also uncover the natural events and domain boundaries of your business processes.
Connect With Us
