Ready to migrate to .NET 10? Check SSW's .NET 10 Migration consulting page.
Migrating from .NET Framework (4.x) to the latest version of .NET brings huge advantages to your app's performance, hosting fees, and maintainability. But it's important that you understand what the road to migrating to the latest .NET will look like for your app before you start breaking things! So how do you ensure your migration is being done the right way?
The need to add new features and functionality to legacy systems is always present - over time it can become continually harder to do this. Additionally, these systems are often built using old tools, SDKs, hosted on outdated platforms, or potentially have increasingly complex (or obsolete) architectures - these all contribute to making it harder to maintain the systems in the first place.
At some point, it becomes necessary to take the existing legacy system and update it to a more modern architecture using newer tools and technologies.
Without focusing on the specific technology - there are 3 main approaches to modernizing applications:
With outdated NuGet packages, legacy C# styles, and older architectures, keeping .NET Framework applications up to date can be painful—especially when you want to jump from .NET Framework to modern .NET for better performance and compatibility.
Luckily Microsoft provides excellent tooling for supporting your great leap into the latest version of .NET.
The differences between a web app built with ASP.NET Framework and one built with ASP.NET Core are immense. The entire request pipeline underwent significant changes, and can often be impossible to migrate in-place. So how can you tackle these challenges the right way?
When upgrading a web application from .NET Framework to .NET Standard or .NET, you will likely have to address System.Web dependencies. Many classes and interfaces in System.Web that enable browser-server communication are removed for .NET Core and .NET 5+, resulting in compile-time error.
There are two primary strategies for migration: a complete rewrite to utilize .NET’s native implementation or the use of System.Web adapters to minimize code changes during an incremental migration.
Some older .NET Framework projects will have EDMX instead of the modern DbContext introduced in Entity Framework 4.1 back in 2012. EDMX projects often use ObjectContext (or an EDMX-generated context that wraps it) for a Database-First approach.
In this rule, we'll refer to ObjectContext (EF6-era API surface) and the EDMX-generated *Entities context (e.g. DataEntities). The generated context typically wraps ObjectContext and is what most application code interacts with.
This rule is written with modern .NET in mind (today that typically means the current LTS, e.g. .NET 10). The steps are still applicable if you’re targeting earlier versions of .NET. The key point is: EDMX is not supported by EF Core, so moving to modern .NET generally requires replacing EDMX-based data access.
The Global.asax is an optional file that dictates how an ASP.NET application handles application, session and request events. The code for handling those events is written in Global.asax.cs, and when migrating to ASP.NET Core this code will need to be restructured.
OWIN is the Open Web Interface for .NET, which was intended to provide a standard interface between .NET web servers and web applications for ASP.NET. It provided the ability to chain middleware together to form pipelines and to register modules.
The Katana libraries provided a flexible set of popular components for OWIN-based web applications. These components were supplied through packages prefixed with Microsoft.Owin.
Middleware and module registering functionality are now core features of ASP.NET Core. Microsoft provides adapters to and from the OWIN interface for ASP.NET that can be used to gradually migrate custom OWIN components. By contrast, ASP.NET Core has native ports for Katana components.
The Web.Config file was used in ASP.NET to control the behaviour of individual ASP.NET applications and configure IIS. By default, modern ASP.NET Core applications use the Kestrel web server which is configured in code. Unless you are deploying your application using IIS, you will need to migrate your Web.Config file.
The Web.Config file contains data about the package inclusions, module inclusions and configuration values.
Migrating your project to the latest version of .NET can be a difficult task, especially when you need to maintain compatibility with older versions of .NET during the transition. Ultimately, we want to get our projects to be running on the newest and latest .NET, but this process often requires careful planning and execution so as to not impact our existing application.
Multi-targeting target framework allows you to build and test your project against multiple versions of .NET simultaneously, which can help you maintain your current application while progressively migrating to the latest .NET version. This will also allow you to identify and resolve compatibility issues early in the migration process, which will save time and effort overall.
This rule provides guidance on how to manage compatibility when transitioning between target frameworks.
Most REST APIs serialise/deserialise to and from JSON format. To perform this serialisation, a .NET web application typically relies on either Newtonsoft.Json or System.Text.Json.
Modern .NET applications prefer System.Text.Json over Newtonsoft.Json - which is commonly found in earlier versions of .NET and .NET Framework projects.
This, however, may break in certain usages.
This issue needs to be addressed when migrating projects from .NET Framework to modern .NET.
In the case where the legacy .NET Framework application is hosting the frontend, it is recommended to also think about the hosting method of the frontend in .NET 10.
This issue is best considered before or during the migration so the team will not spend time implementing something that will be removed shortly in the future.
You finally dumped BinaryFormatter, replaced WebClient with HttpClient, and standardised on System.Text.Json!
But in a big codebase (or with a rotating team) someone can unknowingly add the old API back in a new feature branch, undoing days of migration work. The same risk appears with library preferences: you want everybody using FluentValidation, yet the next pull request pulls in DataAnnotations again.