Migrating from .NET Framework to .NET 8: A Practical Guide

.NET Framework 4.8 is the final major version — it's in maintenance mode with no new features planned. Meanwhile, .NET 8 (and soon .NET 9) offers massive performance improvements, cross-platform support, and modern language features. If you're still on Framework, here's how to migrate pragmatically.

Should You Migrate?

Not every app needs migrating. If it's stable, rarely changes, and doesn't need new features — leave it. .NET Framework 4.8 will receive security patches for as long as Windows Server supports it, which is likely another decade or more. But if you're actively developing the application, struggling with performance, need Linux or container support, or can't hire developers who want to work on legacy tech, it's time to migrate.

The business case for migration usually comes down to three factors: developer productivity (modern .NET has better tooling, faster build times, and hot reload), hosting flexibility (containers, Linux, Azure App Service without Windows licensing), and future-proofing (new libraries, frameworks, and Azure services increasingly target modern .NET only). If any of these matter to your organisation, migration is worth the investment.

There's also a recruitment angle that's easy to underestimate. Senior .NET developers increasingly filter job searches by framework version. A job posting that mentions .NET Framework or Web Forms attracts a smaller, less enthusiastic candidate pool than one advertising .NET 8 and ASP.NET Core. If you're struggling to hire, the technology stack might be part of the problem.

Step 1: Assess Your Codebase

Run the .NET Upgrade Assistant — it analyses your solution and flags incompatibilities. Also check your NuGet packages: are .NET 8-compatible versions available? Any dependencies on Windows-only APIs like WCF client, System.Drawing, or registry access?

The biggest blockers I've seen in government projects are heavy use of Web Forms (no equivalent in modern .NET — you need to rewrite these pages), WCF services (need replacing with gRPC or REST), OWIN/Katana middleware (replaced by ASP.NET Core's middleware pipeline), and heavy reliance on System.Web (the entire namespace doesn't exist in modern .NET).

Create a spreadsheet listing every project in your solution with columns for target framework, NuGet package compatibility, Windows-only API usage, and estimated migration effort. This gives you a clear picture of the total scope and helps you prioritise which projects to migrate first. Class libraries with no Windows dependencies are typically the easiest starting point.

Don't forget to assess your build and deployment pipeline. If you're using MSBuild targets or custom build scripts that depend on Framework-specific tooling, these will need updating too. Similarly, if your deployment process copies specific DLLs from a bin folder, it won't work with the self-contained deployment model that modern .NET encourages.

Step 2: Prepare Your Solution

Before touching the target framework, clean up. Update all NuGet packages to their latest Framework-compatible versions. Replace deprecated APIs. Add unit tests for critical paths if you don't have them — you'll need them to verify nothing breaks during migration.

This preparation phase is where most teams cut corners and then pay for it later. Migrating and modernising simultaneously multiplies complexity. If you update packages, replace deprecated code, and change the target framework all at once, debugging failures becomes nearly impossible because you can't tell which change caused the problem.

I also recommend switching to the SDK-style project format before migrating frameworks. The old verbose .csproj format with hundreds of lines of item groups can be converted to the new SDK-style format while still targeting .NET Framework. This makes the actual framework change much cleaner because the project file is already in modern format.

If your solution uses packages.config for NuGet management, migrate to PackageReference first. Again, this can be done while still targeting Framework and eliminates one of the most common sources of confusion during migration.

Step 3: Migrate Incrementally

Start with class libraries — they're usually the easiest. Convert them to .NET Standard 2.0 first (compatible with both Framework and modern .NET), then move to .NET 8 once all consumers are ready. Use multi-targeting if you need to support both temporarily.

For web projects, the jump from MVC 5 to ASP.NET Core MVC is the biggest change. The request pipeline, dependency injection, configuration system, and middleware model are all different. Plan for this to take the most time and the most testing effort.

The order I recommend for a typical solution: shared utility libraries first (these usually have no Framework dependencies), then data access libraries (Entity Framework 6 to EF Core is a significant but well-documented migration), then business logic layers, then service/API projects, and finally the web frontend project last because it has the most Framework-specific code.

At each step, verify that the migrated project compiles, tests pass, and the application still functions correctly. Don't batch multiple project migrations into a single commit — if something breaks, you need to know exactly which change caused it.

Step 4: Handle Breaking Changes

The most common issues I encounter across government and enterprise migrations include System.Web references (the entire namespace doesn't exist in ASP.NET Core — you need to use the new abstractions in Microsoft.AspNetCore.Http), HttpContext.Current (replaced by IHttpContextAccessor, injected via dependency injection), bundling and minification (the built-in ASP.NET Framework bundling has no direct equivalent — use LibMan, WebPack, or a similar build tool), authentication and authorization (ASP.NET Core Identity is significantly different from FormsAuthentication or the old Membership providers), and Global.asax (replaced by Program.cs and the middleware pipeline).

Configuration is another area where the patterns change completely. Web.config with ConfigurationManager is replaced by appsettings.json with the Options pattern. Environment-specific configuration uses appsettings.Development.json rather than config transforms. This is actually a significant improvement once you get used to it, but it requires rewriting how your application reads configuration values.

Dependency injection deserves special attention. If your Framework application doesn't use DI (many older applications resolve dependencies manually or use Service Locator), you'll need to refactor as you migrate. ASP.NET Core's entire architecture assumes DI — controllers, middleware, services, and configuration are all resolved through the DI container. This is usually the healthiest change in the entire migration, but it touches a lot of code.

Step 5: Test and Deploy

Run your full test suite. If you added tests during the preparation phase, they'll prove their value here. Do side-by-side deployment if possible — run the old and new versions in parallel and compare outputs. Monitor performance closely after deployment: .NET 8 is generally faster, but behaviour differences can surface in edge cases.

Pay particular attention to DateTime handling (subtle differences exist between Framework and modern .NET in how certain date formats are parsed), string comparison behaviour (the default culture-sensitive comparison rules have changed in some cases), and JSON serialisation (if you're switching from Newtonsoft.Json to System.Text.Json, the default behaviour is different for property naming, null handling, and enum serialisation).

For the deployment itself, modern .NET gives you options that Framework didn't. You can deploy as a framework-dependent application (smaller deployment, requires the runtime on the server), a self-contained application (larger but includes everything needed to run), or a single-file executable. For most web applications, framework-dependent deployment to Azure App Service or a Linux container is the simplest approach.

The Payoff

Faster execution — 2 to 5 times in many scenarios, with particularly dramatic improvements in JSON processing, collection operations, and async I/O. Lower memory usage due to improved garbage collection. Native container support, so you can deploy to Docker and Kubernetes without workarounds. Modern C# language features including records, pattern matching, global usings, and file-scoped namespaces. And a framework that's actively developed with major releases every year, a vibrant ecosystem of libraries, and strong community support.

Perhaps most importantly, you'll find it much easier to hire and retain developers. The modern .NET ecosystem is thriving, and developers actively seek out projects using current technology. The migration pays dividends not just in performance and capabilities, but in team morale and recruitment.

Need Help?

I specialise in migrating .NET Framework applications to modern .NET. If you're considering a migration and want an honest assessment of effort and complexity, get in touch for a free 30-minute discovery call. I'll review your situation and give you a realistic picture of what's involved.

Need Help With This?

I offer consulting and hands-on development for .NET, Azure, and DevOps projects. Let's talk about how I can help.

Get in Touch →