Migrating .Net framework applications to .Net Core

Migrating existing applications to .Net Core is not a trivial undertaking so you need to be certain of the potential benefits before you start.

These benefits hinge around two main areas: the potential for cross-platform operation and the easier deployment afforded by the absence of a machine-wide framework. If spinning up containerised services across different environments does not appeal, then you may be better off staying put.

It's all about dependencies

In practical terms, one of the more immediate differences with .Net Core is that it is much more of a package-based eco-system. You use NuGet to include core functionality rather than relying on a system-wide framework. This can take some getting used to as you hunt around for the right package rather than having it waiting for you.

The arrangement of these packages isn't always that logical. For example, adding any data annotations to Entity Framework 7 data classes requires a different package to the core framework. MSTest is split into two separate packages: the MSTest.TestFramework package containing the test API and the less obviously named dotnet-test-mstest package for runtime integration with Visual Studio.

Exclusions

Although .Net Core development will feel familiar to anybody who has done any hard time with the .Net Framework it is not a straight port. Some libraries and APIs were excluded from initial releases of .Net Core as they were considered problematic or undesirable.

This has included some widely used features. AppDomain has been removed because of the amount of runtime support it needs. Remoting has been taken out and shot. Reflection has been moved out of the core libraries to better support static linking in the compiler.

Binary serialization has been discontinued because it was felt that it requires too much intimate knowledge of types, including their internal state. The view was that serialization should be implemented on top of the available APIs, e.g. XML, JSON and Protocol Buffers.

Although remoting will not be widely missed, some of these exclusions have raised a few eyebrows. Many applications rely on BinaryFormatter for the speed improvement that it provides over XML and JSON. There was a similar reaction to moving reflection from the System.Type namespace to an extension method. This was seen as a breaking change to the API that required an unnecessary amount of code changes.

The missing chunks of functionality are only part of the problem as the .Net Core team did have taken the opportunity to clean up aspects of the .Net API. This has led to a number of API omissions, some of which are minor (DescriptionAttribute in MS Test), others are more fundamental (Assembly or DataTable). Others have just been moved out to different packages so need a bit of detective work to find (TypeDescriptor).

Microsoft have softened their stance here and started to bring back some of these APIs. Both binary serialization and reflection are being restored to the Core to make the process of porting a little less painful. The idea is to reduce the friction involved in moving code over to the new runtime.

Coming soon…

There are also a number of APIs that are considered “candidates for porting” even if they do not have a direct implementation yet. Given that .Net Core is a work in progress this is subject to change but it currently includes major features such as sending emails, handling transactions, communicating with LDAP or Active Directory, working with serial ports and using the Windows Workflow Foundation.

Possibly the biggest challenge for any code base is dealing with third party dependencies. You may find that there is no .Net Core implementation for a key part of your application and a migration decision might ultimately rest on how much refactoring is required to replace missing or changed components.

In many cases you are dependent upon communities developing a port to .Net Core, which is by no means guaranteed. For example, attempts by the log4net community to migrate have been hampered by a lack of participants. There has also been some uncertainty over approach, i.e. release a port now, wait for the core APIs to settle down, or just rely on the new Core logging abstractions.

The migration process

Given the changes that have been made to the project structure the only realistic option for a port is to create a fresh set of projects. The configuration system has been completely replaced and any project-level settings now require you to jockey around with JSON files.

A full migration is an iterative process that involves slowly removing any code or APIs that do not have a .Net Core equivalent. Once the code is fit for migration it can be dropped into a freshly created set of .Net Core projects that mirror the existing framework projects.

The API Port tool is an essential part of this process. It is a command line tool that gives you a percentage for each assembly indicating how much framework usage is portable to .Net core. More importantly, it provides a list of the APIs that are not portable including a list of recommended changes where they exist.

To migrate a project you should start by targeting version 4.6.1 or later of the .Net framework. This version of the framework includes all the API alternatives introduced for cases where .Net Core is unable to support existing APIs.

Once you have compiled to 4.6.1 you should run API Port against your binaries and address outlined by the tool. Once the tool reports a clean bill of health you are in a position to create new projects and drop your code into them. This approach should minimise the amount of time you'll have to spend wrestling with a project that doesn't compile.

Not every application can or should be migrated

Not every application would benefit from a migration to .Net Core. In some cases, such as websites build using WebForms or desktop applications using WPF, a migration might not even be possible without a complete re-write. Applications built using a large number of third party dependencies might also find their migration path blocked by the lack of any Core implementations.

This is not the end of the world. Microsoft plan to continue support for the .Net framework for many years to come and see it sitting alongside .Net Core in their development stack. This is more of an alternative path than an upgrade path.