Back to blog

Migration

Software migration in practice: .NET to .NET Core

This is the fourth part of our series on migration — you'll find the introduction here. A close look at moving from classic .NET Framework to modern .NET (Core).

SvK by Sven von Känel 15 min read
  • Migration
  • .NET
  • .NET Core

Software migration in practice: .NET to .NET Core

This is the fourth part of our series on migration — you'll find the introduction here.

While the first three articles on software migration looked at reasons, options and strategies in general terms, today we turn to a current challenge facing more and more companies: the migration from classic .NET Framework (versions up to 4.8.1) to .NET Core (the original name, with the long-term-support versions 2.1 LTS and 3.1 LTS) — or, since 2020 (when .NET 5.0 appeared), just ".NET". The current LTS release since November 2023 is version 8.

A short history of the .NET Framework

The .NET Framework was first released in 2000 as a comprehensive platform for building Windows applications and, later, web applications (ASP, ASP.NET). One of its distinctive features was support for several programming languages such as C#, VB.NET and F#.

Over time, however, a few challenges became apparent:

  • Monolithic nature: the .NET Framework was large and contained many components that weren't always needed (more than 10,000 classes across over 200 namespaces).

  • Platform dependency: classic .NET Framework is largely confined to Windows.

.NET Core was developed from 2016 onwards (v1.0 LTS) in response to these challenges:

  • It was more modular, so developers could pull in only the parts they needed (via the NuGet package manager).

  • It was cross-platform and supported Windows, Linux and macOS.

  • It was open source with an active community.

With the release of .NET 5.0 in 2020, .NET Core was officially renamed to .NET. ASP.NET Core and Entity Framework Core followed the same path and were also renamed.

This change was meant to unify the platforms and send a clear message: .NET is now the central platform for all applications. The most recent step in this consolidation was the integration of the acquired Xamarin ("Mono") platform for mobile systems (iOS, Android, …) into .NET in the form of the MAUI framework.

In short, .NET 8 has reached a level of maturity that makes migrating large .NET 4.x applications considerably easier than earlier .NET (Core) versions did — those early releases came with API changes and incomplete or missing APIs that posed real problems for any migration project.

Reasons to migrate to the current .NET (Core) version

Migrating a large software project from classic .NET Framework to the latest version of .NET Core (now .NET 8) offers a number of compelling benefits:

  1. Open source and cross-platform compatibility:

    .NET (Core) is an open-source framework with access to the source code and a large community — and therefore a large pool of potentially available developers. Applications can target Windows, macOS, Linux and mobile devices. Windows, macOS and Linux can all serve as development platforms too. Note: Visual Studio 2022 for Mac was discontinued on 31 August 2024, but it can be replaced with Visual Studio Code with relatively little friction.

  2. Modularity and lower resource consumption:

    .NET (Core) takes a modular approach (via the NuGet package manager) that lets you include only the features and APIs you actually need. The result is a smaller application footprint and leaner, faster apps.

  3. Improved performance: .NET (Core) is geared towards performance. Thanks to extensive optimisations from Microsoft, it offers better execution speed, lower memory consumption and improved scalability compared with the older .NET Framework. Applications built on .NET (Core) generally perform better under high concurrency or heavy load. You can feel this in everyday use, too — applications simply run more smoothly.

  4. Greater scalability: .NET (Core) applications can handle more concurrent requests because they're more lightweight, use resources efficiently, and offer new features such as native compilation. If your software project needs to scale horizontally (for example, with a microservices architecture), .NET (Core) is, in our experience, the better choice. On certain projects (e.g. RADIUS authentication) we at evanto have achieved throughput of several thousand requests per second on standard hardware.

  5. A modern development experience:

    .NET (Core) integrates seamlessly with modern development tools such as Visual Studio Code (free) and Visual Studio 2022. You get efficient access to the latest language features, libraries and tools, plus high developer productivity. Documentation quality is high, and thanks to widespread adoption there are countless resources on the web. New and especially efficient is the direct AI integration via GitHub Copilot in the IDEs, which noticeably eases repetitive work.

  6. Long-term support (LTS):

    With the release of .NET 8, Microsoft has committed to long-term support (LTS) for .NET Core. This means applications receive security updates and bug fixes over a predictable, longer time frame (three years for LTS versions, with .NET 8 supported until 10 November 2026).

  7. Cost savings and talent pool:

    .NET (Core) reduces licensing costs because it's multi-platform open source and doesn't require expensive Windows Server licences. With support for the widely deployed Windows Server 2012 R2 having ended, this is, in our experience, an important factor — after a migration, applications can be hosted comparatively easily on a Linux server or in a container-based environment (Docker, Podman, K8s).

    Equally important: although the talent pool for classic .NET Framework is still sizeable, the trend is shifting clearly towards .NET (Core), as developers prefer to work with newer technologies.

  8. Future-proofing:

    Microsoft has frozen the functionality of classic .NET Framework at its current state; all innovation and extension happens in .NET (Core). As of early 2024 there are already many important additions — such as Blazor, ASP.NET Minimal APIs, MAUI, OpenTelemetry, Aspire, gRPC and more — that are only available in .NET (Core). The same applies to language features such as Spans and to new features of the Garbage Collector (GC) and the JIT compiler (DATAS, Dynamic PGO, more). And third-party support for the old framework is steadily fading.

For any application that needs to remain viable in the long term, migrating to .NET (Core) is therefore close to non-negotiable.

Parts of the .NET Framework that have been dropped

.NET (Core) covers all the important capabilities of the classic .NET Framework, but a (comparatively small) set of areas, APIs and technologies is no longer available and has to be reimplemented. The reason is usually that newer technologies and more efficient approaches now exist. Notably:

  • ASP.NET Web Forms: migration is possible, for instance, to ASP.NET Razor Pages.

  • The Windows Communication Foundation (WCF): this framework was often used for SOAP services, which are still possible via ASP.NET web apps; alternatively, a migration to REST or gRPC services is worth considering (especially where [WebGetAttribute] or [WebInvokeAttribute] was used). Other use cases (server-side) can be migrated with comparatively little effort using the CoreWCF library.

  • The Windows Workflow Foundation (WF): there's no direct migration path within .NET (Core); either move to an alternative workflow engine (e.g. ELSA, Wexflow) or replace it with "classic" code. If Azure is on the table, Durable Functions are another option.

  • Object serialisation via BinaryFormatter (marked deprecated). Depending on the use case, replacements are usually possible via System.Text.Json, Newtonsoft.Json or other tools.

  • Entity Framework 6 has been completely rewritten, and the visual EDMX models are gone. They need to be replaced via either the database first approach (model generated automatically from the database structure) or the code first approach (database generated from C# model classes plus context).

  • Other Windows-only APIs such as support for ODBC, drawing, the Windows Registry, the event log, Windows Services, and so on. Much of this can be brought back — at least for the Windows runtime platform — via the Windows Compatibility Pack (see below), but it's better to reimplement these parts with multiplatform support (where possible).

  • AppDomains are not replaceable.

  • Further technologies that are no longer available, such as .NET Remoting, can be found here.

Other, more complex parts of .NET such as ASP.NET MVC are still around, but with changes and new features in some areas (e.g. Tag Helpers, the replacement of @Html.Action() with ViewComponents, filters, and so on).

For any migration project, all third-party libraries must also be checked for availability and compatibility (API changes) and replaced if necessary. Some functionality that previously required third-party libraries (e.g. dependency injection via AutoFac, LightCore or Ninject) is now available faster and more efficiently directly in .NET (Core). When that's the case, object lifetime settings (Singleton, Scoped, Transient, …) should be reviewed.

For other concerns — such as application configuration — the existing mechanisms (e.g. ConfigurationManager) are still available, but newer options such as IConfiguration are far more flexible, so switching over makes a lot of sense.

Options for the UI migration

Under .NET (Core) 8 there are noticeably more options for building application UIs than there were in classic .NET:

  • ASP.NET Core Blazor: a framework for building interactive web UIs with C# and HTML. Blazor lets you write client-side logic in .NET without using JavaScript. It supports both server-side rendering and WebAssembly to run UIs in the browser. Blazor offers components, routing, dependency injection and other features that simplify web development. New in .NET 8 is the "Auto Render Mode", which decides automatically, on a per-component basis, whether SSR (Server-Side Rendering) or CSR (Client-Side Rendering) is required.

  • MAUI: a cross-platform UI technology based on Xamarin.Forms. MAUI stands for Multi-platform App UI and lets you build native UIs for Windows, macOS, Android and iOS from a single codebase. MAUI uses XAML as its UI markup language and offers a range of controls, layouts and styles that adapt to each platform.

  • WinUI 3: a modern UI technology for Windows apps, built on top of the Windows UI Framework (UWP). WinUI 3 provides a set of UI controls, animations, effects and other features that improve Windows app development. It allows you to build both desktop and UWP apps from the same UI code and also integrates with other UI technologies such as WPF and WinForms.

  • ASP.NET Minimal APIs: with a focus on simplicity, compactness and performance, .NET now offers a Node.js-like development style for web applications. A key advantage is support for native AOT (Ahead-of-Time) compilation, which can deliver significant performance gains.

The "classic" technologies are still around as well:

  • WinForms: a simple UI technology without MVVM data-binding mechanisms — still popular in many places. WinForms is not multiplatform and only available on Windows.

  • Windows Presentation Foundation (WPF): a more modern UI technology — based on MVVM data binding — introduced with .NET Framework 3.5 and still available in .NET 8. There are, however, doubts about its long-term future, as WPF is also a Windows-only technology.

  • ASP.NET MVC: the MVC (Model-View-Controller) framework remains the tool of choice for building complex web applications, since newer technologies and extensions such as Minimal APIs or Razor Pages focus primarily on the fast implementation of less complex tasks.

In addition, there are a number of third-party UI technologies such as Uno Platform or Avalonia for building multiplatform apps.

It's always an option, too, to pair an ASP.NET back-end service with a well-established technology for building single-page applications (SPA) and create the front end with:

A migration is therefore advisable — or, where the source technology is no longer available (see above), mandatory — for the following starting points:

  • ASP.NET Web Pages → e.g. Razor Pages

  • WinForms → WinUI 3 or a web UI (MAUI, Blazor, ASP.NET)

  • WPF → WinUI 3 or a web UI (MAUI, Blazor, ASP.NET)

Adjustments are also needed in frameworks that are still around, such as ASP.NET MVC — for example with Owin controllers and Startup classes:

  • Namespace System.Web.Http/Mvc becomes Microsoft.AspNetCore.Mvc
  • Base class ApiControllerController
  • Replace HttpRequest, HttpResponse, HttpResponseException
  • Replace IHttpActionResult with IActionResult
  • HttpContext.Current is no longer available
  • File uploads need adjustments too (old: HttpPostedFileBase; new: IFormFile or MultiPartReader)
  • ASP.NET Core uses System.Text.Json instead of Newtonsoft.Json for serialisation, so Newtonsoft.Json-specific artefacts such as JObject or JToken should be avoided (though Newtonsoft.Json can still be added back in if needed).

Tools that support a migration

The following Microsoft tools are available to help with a migration:

  • .NET Upgrade Assistant: this is a Visual Studio extension and command-line tool that helps update apps to the latest version of .NET. It supports various languages, project types and upgrade paths, for example from .NET Framework to .NET, from .NET Core to .NET, from UWP to WinUI 3 or from Xamarin.Forms to .NET MAUI. It can also make some changes to your project and code automatically, fixing some incompatible changes and taking advantage of newer features.

  • Platform Compatibility Analyzer: part of the .NET SDK (one of the Roslyn Code Quality Analyzers), used to check up front which APIs in your project are not multiplatform-capable.

  • Windows Compatibility Pack: a NuGet package that provides a large portion of the .NET Framework APIs that aren't part of .NET (Core). This mostly covers APIs that rely on Windows-specific technologies, such as the Windows Registry or the GDI+ drawing model.

A key point to remember: while the Upgrade Assistant can take some of the work off your shoulders — for instance when converting project files to the new format — much of the remaining effort is still manual (e.g. replacing libraries and services that are no longer available for .NET (Core)).

Planning a .NET Core migration

In addition to the steps described in the previous article "Phases of a software migration project", the following aspects are important during the detailed design phase of a .NET application migration:

  • (External) dependency check: which third-party libraries are no longer available for .NET 8?

  • Check which parts of the application can be migrated automatically, for instance with the .NET Upgrade Assistant (see above).

  • Detailed migration analysis based on the components and APIs contained in the application to be migrated (e.g. ASP.NET changes).

  • A plan for using new APIs and features in .NET (Core), e.g. for cross-cutting concerns such as logging, tracing, dependency injection, authentication, etc.

  • Investigations into multiplatform readiness: it's not only about checking cross-platform availability of all APIs, but also things like hard-coded paths and file-system access, since UNIX-based systems use different path separators and are case-sensitive about directory and file names.

  • Possibly adapt the test and deployment strategy (look into new options such as container-based deployment or Azure cloud deployment).

  • Possibly adapt the developer platform and tooling, check licensing (e.g. GitHub Copilot) and hardware requirements for developer workstations (especially when mobile development tools / MAUI are involved).

  • Possibly provide test hardware (mobile development).

  • (Internal) dependency analysis — and, on that basis, an order in which the project's assemblies will be migrated.

Diagram showing migration approaches for .NET applications: .NET Standard libraries, multi-targeting, and service-based decomposition.

Since it's usually not possible to pause development of the existing (classic) .NET system during the migration phase, a big bang migration (switching everything in one step) is often out of the question — otherwise every change in the existing system has to be replayed in the already migrated parts. For a step-by-step migration (feature by feature), .NET offers the approaches shown in the diagram:

  • Using .NET Standard libraries: .NET Standard describes a common API surface that's available in both .NET 4.x and .NET (Core). Assemblies in the existing system are compiled with the moniker (target environment) netstandard-2.x and can then be used both in the existing application and in the .NET Core project. Downside: .NET Standard 2.x only covers a subset of what's available in .NET (Core).
  • Multi-targeting: an assembly's project file lists both net48 and net8.0 as target frameworks, and (not yet migrated) differences are bridged via conditional compilation. For libraries that are very close to Windows, the net8.0-windows moniker can also be used, which gives access to Windows-specific APIs — multiplatform readiness, however, is only achieved once these have been migrated to a platform-independent form. Downside: increased effort due to multiple migration steps and conditional compilation.
  • Services: the architecture of the existing application is adjusted before the migration so that important — or even all — back-end functionality is exposed as services that can then gradually be migrated to .NET (Core). In the meantime they're available both to the existing (UI) application and to the already migrated parts. Downside: changes to the existing application are required before the migration begins.

We're happy to help with this complex topic!

If you'd like to discuss your migration project with us, please get in touch via our contact page.

NEWSLETTER

Four to six times a year, no marketing noise.

One pattern, one case, one recommendation. Signup with double opt-in, unsubscribe at any time.