Migrating a large code base from .NET 4 to .NET (Core)
As Microsoft has decided to discontinue the .NET framework and only move forward with .NET (former .NET Core), many projects need now to consider if they should migrate to .NET (Core).
As the .NET framework and .NET (Core) differ in many important parts (see e.g. Microsoft guideline for choosing between .NET and .NET Core ), it’s not as simple as just changing the compilation target, especially for large projects. But although it may be quite some effort to port your code to .NET (Core), it will be beneficial in many scenarios:
- .NET (Core) offers the best possible performance and scalability
- It’s much more suitable if you want to host your .NET runtime entities in containers.
- Only .NET (Core) allows to use latest innovations like new language features (everything introduced since .NET Core 3.x / C#8). A good description on these language features (like non-nullable reference types) can be found in the documentation from Microsoft: what’s new in C# 8 and what’s new in C# 9.
Also any libraries leveraging .NET Standard 2.1+ can’t be used anymore from .NET 4.8.
In this article, you will find hints and descriptions of the steps needed to successfully migrate a large code base to .NET (Core) without stopping the normal development of the project for multiple weeks or months. This know-how is an outcome of our experience at ELCA migrating many small to large projects for our clients.
Analyze the code and setup the migration plan
For a successful migration, it’s crucial to first analyze your code base and make a migration plan for an incremental migration. Migrating everything in one go without continuing the normal development work is normally not an option for a large code base.
As a first step to come up with a plan, you need to identify which parts of your code base are no longer supported directly by .NET (Core). Microsoft provides a helper tool to identify such parts. For each of the identified parts you need then to identify a suitable replacement. Based on this, you can then come up with a plan for the migration.
Such a plan normally contains 2 large parts: -1- remove the blockers for a migration, and -2- perform the migration itself.
Replace dependencies preventing a migration
Upgrade/replace third party libraries/dependencies
If there is a newer version available supporting .NET (Core) or .NET Standard, upgrade to this version. If neither .NET (Core) nor .NET Standard is supported, replace the library.
Alternatively, if the libraries are open source, you could also consider porting the library yourself. For very central libraries, that could be a good option. At ELCA, we have chosen this approach for Ninject.Web.Mvc and have created Ninject.Web.AspNetCore. This allowed us to keep using Ninject, a replacement would have been expensive and difficult due to missing features of e.g. the Microsoft ASP.NET Core DI Container.
Replace technologies no longer supported in .NET (Core)
Many Microsoft’ frameworks and libraries are not supported anymore in .Net (Core). For example: WCF, WF, .NET Remoting. For the publication technologies, choose which replacement suits best. WCF could e.g. be replaced by ASP.NET Core quite easily in some scenarios.
As ASP.NET Core 2.2. is .NET Standard 2 compliant, it can be used from .NET 4.8 as well as from .NET Core 2.2. For an incremental migration, it’s important to choose here an approach, which supports both .NET 4.8 as well as .NET (Core), i.e. it must support .NET Standard or have builds for .NET Framework and .NET (Core) for the same library version.
Below, you can find some detailed considerations for replacing WCF.
Migrate your projects to .NET (Core)
Migrate to the new SDK project format
This format can still target .NET 4.8, but it simplifies the migration later on as only the new project format supports compilation to .NET (Core).
There are some tools to automate this, but you need to verify the output and manually fix some stuff potentially.
Migrate your library projects in a first step to .NET standard instead of .NET (Core) directly
This allows to use them from both .NET framework as well as .NET (Core). Alternatively you could also multitarget your library to .NET 4.8 and .NET (Core) if you need to add specific code for different target frameworks.
Migrate the application projects (console, desktop, windows services, web apps) and the unit test projects to .NET (Core)
Runnable projects can’t target .NET standard. They must define the runtime to be used.
Replacing WCF
One of the most challenging part in the migration of our large code base was to replace the WCF server part (WCF client is still supported in .NET (Core)).
There are multiple options available to do this, but they have different advantages and drawbacks and which one is best depends on your requirements. The following could be done e.g:
- Migrate to ASP.NET Core (and use Web APIs)
A migration to ASP.NET Core Web APIs could be quite easy if you are using the pattern of request message and response message for your WCF services, and not using multiple method parameters or ref/out arguments.
NewtonSoft Json.NET supports data contracts for JSON serialization, so if you didn’t use some very special serialization mechanisms, the request and response could just as well be serialized to JSON.
The single request/response pattern easily maps to POST requests.
Based on the extensibility of the ASP.NET Core (it includes a lot of customization possibilities), it’s then not needed to adapt the WCF service implementations, they can just be published as ASP.NET Core Services instead of WCF services (even in parallel for some time to simplify the migration even more).
This can be achieved building the ApplicationModel yourself instead of using the auto discovery of ASP.NET Core. By building the ApplicationModel you can register your WCF services instead of adding all the ASP.NET Core attributes to your WCF services.
On the client side, it’s easy to create dynamic client proxies for the Web APIs and keep the client code the same (by taking advantage of the fact, that we use POST requests only on the server side).
Before the migration, our code for publishing, implementing and consuming WCF services looked like this for any service (named ExampleService for simplicity):
After the migration, our code for publishing, implementing and consuming Web APIs services looked like this for any service (named ExampleService for simplicity):
- Use CoreWCF
It’s meant as a replacement for WCF but only supports a limited feature set. Depending on the features used, it may work or it may not. - Use SoapCore
It’s a good option to implement SOAP web services with ASP.NET Core. But it only works well, if you built interoperable web services and didn’t just use WCF for .NET to .NET communication. - Use gRPC
gRPC uses the concept of interface definition languages which is an additional complexity to handle and it requires all clients of your services to also support/use this technology.
Conclusion
With a good plan, it’s feasible to migrate a large code base from .NET 4.8 to .NET (Core) in parallel to continuation of the normal development work. As .NET 4.8 framework and .NET (Core) both support .NET Standard 2.0 and many third parties and Microsoft libraries target .NET Standard 2.0 (e.g.ASP.NET Core 2.2), all the required building blocks for such a migration are available.
Based on this methodology, in one of ELCA’s big projects, we have migrated a code base of around 500'000 lines of C# in 3 sprints of 4 weeks in parallel to the normal development. The effort needed was around 1 man month to perform some experiments and create the migration plan and around 3–4 man months for the migration itself. During the initial migration, some improvement possibilities were identified, which were implemented afterwards in an additional 1–2 man month. At most 3–4 developers worked on the migration whereas more than 15 worked on the normal business features.
Ending, it has been very beneficial to do this migration to stay up to date with the latest innovations and benefit from performance improvements.