Thursday, 14 June 2018

Porting .Net Framework to .Net Core is not a 5 minute job!

There are lots of things to like about DotNet core. It is much faster than the dotnet framework; it has better abstractions which has allowed genuine cross-platform functionality and it has used the best-practices of software development like Dependency Injection to remove much more of the smoke and mirrors and legacy code that underpinned the dotnet framework!

Should you port?

In most situations, you don't get many chances to make a big code change to an existing application, especially if it was written for a Customer who often won't pay for the update. There are benefits for maintenance and performance as well as cross-platform capability but these are marginal benefits for most businesses who would rather throw more hardware at the problem than the time, money and risk of something bigger.

If you are about to do a major revamp/rewrite, why not do it at the same time. It will take longer but not as long as doing it in two separate stages! You get to share the system testing time between your design changes and the framework updates!

Technically though, there is lots to like about dotnet core. It always feels nice to do things the right way and not to have magic global or static variables doing unexpected things and which make it hard to Unit Test properly.

On the other hand, there is some functionality, particularly in third-party libraries that is not ported to DotNet core and which might or might not have an impact on your application.

What do you need to know?

Firstly, you should know that we are currently on dotnet core 2.x and there were a number of changes from 1.x (as expected) so be careful with instructions on the web, which might apply to older versions and which might not work properly or at all on 2.x Some things have also been made easier, which is nice.

If you are porting a library, you can use .Net Standard, which is specifically for shared libraries since it can support dotnet core and dotnet framework 4.x, which can be useful for porting in stages. You can start with the libraries and then work up to the application.

Applications are not .Net Standard because they are not shared. New applications can either be dotnet framework 4.x or dotnet core 2.x

As mentioned before, not everything is the same and some things are not supported in dotnet core so don't burn any bridges when planning the work.

How to port

In general, the easiest way to port is to create a completely new dotnet core or dotnet standard project and then copy in your original code. This ensures you don't end up keeping orphaned files, especially things like AssemblyInfo.cs, which your build tools might be using even though it is not used in dotnet core. This doesn't mean you have to lose your history though. Although many things will change, you can still do it in a branch and simply merge it over the original code. If you try and leave the structure as close as possible, then it might not look too bad on the history! You can then make "work arising changes" later.

Things that change

There are various things that will have to change and the amount of work will depend partly on how well your existing code is written: if you already use DI, for example, it will work much more easily. Also, if you have used a lot of System.Web code, you will have more work. If your current code is already MVC then there will still be changes but the idea of controllers and actions still holds in the same way.

If your current project is really old web forms then you will basically need to bin it and start again as you would if you were porting web forms to non-dotnet core MVC. The whole mindset is different and porting away from web forms is a mammoth job that generally involves going back to the spec and writing the system again!

Dependency Injection

This is a cornerstone of dotnet core. The whole framework is written around dependency injection and since it is built-in, you no longer need to install a DI framework, although you still can if you are happier with what you already know. If you want remove the debt of a third-party framework when porting (we removed autofac and used the MS one) then you will need to spend some time reworking it because they all setup slightly differently and MS don't support all the mechanisms that autofac does like named registrations.

DI can be done badly so if you haven't used it yet, get a good handle on what it is so that you don't create any anti-patterns or code smell in your efforts!

Configuration

Configuration in the dotnet framework was all about web/app.config and the helpers like WebConfigurationManager were very much about IIS. This has all gone in dotnet core. Although you can use XML config files, the standard method is to use json, which can be cascaded by environments like web configs could be and not just for the main config, for any config!

Whole sections of web server management are now unavailable in dotnet core since the configuration would be web-server specific and not appropriate for an app configuration.

The best way to use config now is to bind it to POCO objects so it can be injected into services really easily. It can either be registered using services.Configure (which allows it to be injected using IOptions) or you can create concrete config objects that can be registered like any other singletons. Doing it properly allows hierarchy of configs and notification of config changes. The mechanism also allows environment variables to be bound to the same config so that environment variables or app settings on Azure can override config items in your json files at runtime.

MVC and Web API

The return types from MVC and web api have finally been merged into a single type called IActionResult, which is not the same as the original IHttpActionResult in MVC. There will be some changes here including different Controller methods to return various responses. For example, you used to have to call Content(HttpStatusCode.OK,object), whereas now there is an Ok(object) method. Many of these have changed slightly so you will have some work to do there. Status() now takes a StatusCodes enumeration which is named more verbosely but more usefully such as StatusCodes.Status500InternalServerError.

General Libraries

Some of the libraries you are using might well have their architecture changed for .Net Standard/.Net Core to match the use of DI. For example, we use to have a helper library where we would call IMainInterface.GetSessionUtilities() to return an ISessionUtilities but now we are using DI, the main interface as no value and I can simply inject ISessionUtilities directly to the controller. There were lots of changes due to this.

Unit Testing

You can still theoretically unit test dot net core apps with non dot net core Unit Test projects but you will probably face problems if you are using json config files that the normal unit test projects won't understand (although I think you can run the old style project using "dotnet test").

It is easier to create a dotnet core test project which has the same kinds of changes as normal dot net core apps but which will play much more nicely with injection and configuration.

The amount of work to change your original unit tests will depend massively on how much you had to change your app during the port. It could be anything from minor changes to inject configuration through to massive DI and re-factoring due to major re-architecting. In my case, the Unit Test updates took much longer than the main web service!

Conclusion

There should not be anything that hard to port as long as you understand the high level differences and you haven't done anything crazy in your existing app but just because it isn't hard, doesn't mean it won't take a long time just to go through things and unpick them. You should also allocate a large amount of testing time so that you can try and touch all the lines of code and make sure any changes you've made haven't broken anything!
Post a Comment