Articles

Upgrading .NET Framework to modern .NET

CMT Software. Published

Most .NET Framework applications can move to modern .NET, but rarely in one go. The safe route is to work out what the application depends on, move the shared libraries first, and handle the hard parts (Web Forms, WCF services, AppDomains, Windows-only APIs and old third-party packages) as separate pieces of work.

Why upgrade from .NET Framework to modern .NET?

Modern .NET is the cross-platform successor to .NET Framework that began as .NET Core. .NET Framework 4.8 is the last major version; 4.8.1 is a minor update. It is still supported as part of Windows, but it gets fixes rather than new features. All new platform, library and tooling work happens in modern .NET.

Staying on .NET Framework gets more expensive:

  • Newer versions of common libraries drop .NET Framework support.
  • Hosting is tied to Windows, which rules out Linux servers, Linux containers and many cheaper cloud options.
  • Developers who want to work on it get harder to recruit and keep.
  • You can't use the performance improvements in modern .NET.

There's no need for an urgent rewrite. But plan the move while the application still works and the people who understand it are still around.

How do you assess a .NET Framework application for migration?

Inventory the solution

List every project in the solution and sort it into a group:

  • Class libraries with business logic and no UI or hosting code usually move easily.
  • Web applications on ASP.NET MVC or Web API move with rework. Web Forms applications have to be rebuilt.
  • Services: WCF services, Windows services and scheduled console jobs.
  • Desktop applications in Windows Forms or WPF run on modern .NET, but only on Windows.

The dependency graph between the projects tells you the order of work.

Check APIs and packages

Build each library against the target framework to surface missing references and APIs, and turn on the platform compatibility analyser to flag Windows-only calls. Treat the results as a starting list rather than a verdict, because static checks can't see reflection, configuration-driven behaviour or plugins loaded at runtime.

Then check every NuGet package and referenced DLL:

  • Is there a version that targets .NET Standard 2.0 or modern .NET?
  • Is it still maintained?
  • If not, is there a direct replacement?

DLLs with no source code and no vendor support need the most thought.

What are the warning signs of a difficult migration?

These mean more effort than the size of the codebase suggests:

  • AppDomain.CreateDomain, .NET Remoting or Code Access Security.
  • Heavy use of System.Web and HttpContext.Current outside the web layer.
  • Business logic in .aspx code-behind files.
  • BinaryFormatter used for caching, messaging or saved files.
  • Windows Workflow Foundation.
  • Custom configuration sections, web.config transforms and machine-level settings.
  • Little or no automated test coverage.

Agree what "done" means

Decide whether the target is "runs on modern .NET on Windows" or "runs in containers on Linux". The Linux target is the bigger job, because every Windows-only dependency then becomes a blocker.

How do you plan a staged .NET migration?

A staged upgrade keeps the application running and releasable throughout.

1. Prepare on .NET Framework

  • If you're on an older version, move to .NET Framework 4.8 or 4.8.1 first.
  • Convert class library and console .csproj files to the SDK-style format, and switch packages.config to PackageReference. Both work on .NET Framework and make multi-targeting simple. Leave classic ASP.NET web projects alone; they don't convert cleanly and get replaced in stage 4.
  • Update third-party packages to the latest versions that still support .NET Framework.
  • Add tests around the areas you're about to change, even coarse end-to-end ones.

2. Move shared libraries to .NET Standard 2.0 or multi-target them

Multi-targeting means building one library for both .NET Framework and modern .NET. A library that does that, or targets .NET Standard 2.0, can be used by old and new applications at the same time. That is what lets you upgrade in pieces: the existing application keeps running on the same code while new hosts are built on modern .NET.

3. Migrate console jobs, Windows services and small APIs

Console jobs, Windows services and small internal APIs make good early candidates. Pick ones with few dependencies and an easy way back. A Windows service becomes a worker service using the generic host. Getting a few live proves your build, deployment, start-up and monitoring on modern .NET.

4. Move the main application

For ASP.NET MVC and Web API there are two realistic options.

Whole-application migration suits smaller applications. You create a new ASP.NET Core host, move the controllers and views across, and replace Global.asax, HTTP modules and handlers with middleware.

Incremental migration suits larger ones. You put a new ASP.NET Core application in front of the old one using a reverse proxy such as YARP. Routes move to the new application one at a time, and anything not yet moved is passed through to the old one. Microsoft's System.Web adapters help the two share session and authentication, and let shared libraries that use HttpContext compile against ASP.NET Core.

The incremental route takes longer overall, but each release stays small. Both applications run side by side, so the rollback plan has to cover shared session, authentication and database changes as well as routes.

5. Retire the old pieces

Once all traffic and jobs are on modern .NET, remove the .NET Framework targets, the proxy pass-through and any compatibility shims. Leave them in "for now" and you will be maintaining two platforms instead of one.

What blocks a .NET Framework migration, and how do you fix it?

Can ASP.NET Web Forms run on modern .NET?

No. Web Forms doesn't exist in modern .NET and there is no automatic conversion. The pages have to be rebuilt, usually in ASP.NET Core MVC or Razor Pages, or in Blazor where its component model fits better.

The effort depends on where the logic lives. If business rules sit in code-behind files, pull them out into separate classes while still on .NET Framework, and the rebuild becomes mostly UI work. The incremental proxy approach lets you rebuild and release pages a few at a time.

What happens to WCF services on modern .NET?

Calling existing WCF services is usually straightforward. The WCF client libraries are available as NuGet packages for modern .NET and work as long as the bindings and security settings you use are supported.

Hosting WCF services is the hard part, because the server side was not carried over. The options are:

  • CoreWCF, a community-maintained port of the WCF service model supported by Microsoft. Use it where existing clients can't change, after checking it supports their bindings and behaviours.
  • ASP.NET Core Web API with JSON over HTTP, where you control the clients.
  • gRPC, for internal, high-volume service-to-service calls.

What replaces AppDomains and .NET Remoting?

Modern .NET supports only one AppDomain per process, and .NET Remoting has gone. If an application used AppDomains to isolate plugins or unload code, use AssemblyLoadContext for loading and unloading assemblies, or separate processes talking over named pipes, HTTP or gRPC for real isolation.

Code Access Security has no replacement; rely on operating-system and container boundaries.

Do Windows-only APIs work on modern .NET?

Yes, but only on Windows. The registry, Windows event log, WMI, performance counters, System.Drawing.Common and COM interop all still work on modern .NET on Windows, and many come through the Windows Compatibility Pack.

If Windows hosting is acceptable, these APIs aren't blockers. If Linux hosting or Linux containers are the goal, wrap each one behind an interface and replace it. For example, swap System.Drawing for a cross-platform imaging library, and the event log for structured logging.

What if a third-party package doesn't support modern .NET?

Third-party packages are often the slowest part of a .NET migration, because the fix depends on someone else. For each problem package:

  • upgrade it, if a supported version exists
  • replace it with a maintained alternative that does the same job
  • isolate it in a small .NET Framework service that the rest of the system calls over HTTP or a queue

Isolation is a perfectly good long-term answer for an unsupported vendor component, as long as someone clearly owns it and it is documented.

How do you handle configuration, serialisation and data access?

  • Settings in web.config and app.config move to appsettings.json and environment variables. The System.Configuration.ConfigurationManager package bridges the gap.
  • BinaryFormatter has been removed from newer .NET runtimes for security reasons. Replace it with JSON or another explicit format, and plan how to read data already stored in the old format.
  • Entity Framework 6.3 and later run on modern .NET, though EDMX designer models need extra work. That means the move to EF Core can be a separate step, provided your database provider also supports EF 6 on modern .NET.

If you need help

We upgrade .NET Framework applications in stages, alongside your team or on your behalf, and connect them to the rest of your systems as we go. Read more about our legacy modernisation work.

Email [email protected] with your brief.