Observations on Porting from .NET Framework to .NET Core
You’ve heard that .NET has gone open source. You’ve also heard that it has gone cross-platform. And you’ve even heard that Red Hat is shipping a supported version of .NET on Red Hat Enterprise Linux. So maybe you are thinking to yourself, “wow, this is fantastic! I’m going to copy these EXEs and DLLs of my .NET application over to my Red Hat machine and run them!”
Well, unfortunately, it’s not going to be quite that easy. At least not today.
First and foremost, the open source version of .NET is called “.NET Core.” It is available for many platforms, including Windows and Linux. Those .NET projects and applications that you already have running, however, were built on and for .NET Framework. And .NET Framework and .NET Core are not the same thing; they are more like siblings, which also implies that one is not a subset or child of the other.
“Well, then, what’s the point?!”
The good news is that while they are siblings, they do look a lot alike. Although they’re not identical twins, you’ll definitely recognize them as being from the same immediate family. As such, it is possible to port many existing .NET Framework applications to .NET Core.
“How hard is it to port something?”
Which, of course, leads to the standard non-answer in our industry: “it depends.” Sometimes it is easy. Many times, it is difficult. Sometimes it is very difficult. At the root of the issue is the fact that not every API that is in .NET Framework is in .NET Core. It’s worth starting with an understanding of how the .NET Core project decides what parts of the .NET Framework it implements — that philosophy is documented here. That document does not tell you how to port your code, but it’s good to set context.
“Okay, so what kinds of existing .NET apps can I port?”
In general, you can port non-visual applications (i.e. forget about desktop applications). Console applications, web services/Windows services, and web apps are all good candidates. (Yes, you may think of web apps as ‘visual’, but as the web browser does the rendering, I’m classifying them as non-visual.)
“Okay, so how do I actually port my code?”
Let’s start with MSFT’s published guidance; executive summary: first, if you’ve got third party dependencies, this is likely going to be difficult (today). Second, get your code running on .NET Framework 4.6.2. Third, run the ‘apiport’ tool to analyze the code to generate a report of what can, and what cannot, be easily moved to .NET Core. Fourth, deal with it.
Note that in ‘the future’ (I’m hoping for mid-2017), there will be some newer answers to this problem (including dealing with third party dependencies), although I also suspect that those enhancements will be better for someone who stays on Windows (e.g., moves from .NET Framework on Windows to .NET Core on Windows) — particularly if the code makes use of a lot of Windows-specific features (e.g. the Windows registry). More on this in a moment.
I used to get some good mileage out of this Michael Whelan post, although it is getting a bit dated. Still, it’s worth reading.
Porting can be a painstaking process. As such, I honestly do not recommend that anyone attempt to port an existing, complex application. I know that’s a strong statement; let me add some clarity. Porting an existing app, such that the end result is the same app simply running on .NET Core, delivers essentially no value. Given all the risk involved, even if the hope is to leverage the increased performance provided by .NET Core (especially for ASP.NET), honestly, the saner choice is to just run more Windows VMs with the original application.
“So, I guess I can stop reading this blog posting about porting?”
Well, hold on just a sec. If you are willing to rearchitect your application as part of the migration process from .NET Framework to .NET Core, that’s a different story — and there’s where true value can be realized. If you are going to put in the effort to port to your application, combine that with an effort to modernize your application. In fact, breaking up a big monolith, one functional area at a time and reconstituting those areas as microservices, can be a very rewarding endeavor. .NET Core, combined with the other major frameworks like ASP.NET Core and Entity Framework Core, coupled with something like the Orleans project [https://dotnetfoundation.org/orleans] are perfect for writing microservice-based applications — and worthy of deployment onto an orchestrated container platform such as OpenShift. True value, in terms of things like scalability, reliability, continuous value delivery, operational costs, and so on, can be realized.
“What about Mono? Doesn’t that give me the full .NET Framework on Linux? It’s open source.”
Maybe you’ve heard about Mono and think, “hey, that solves the problem!” Mono is an open source implementation of some parts of the .NET Framework. It’s even now a part of the .NET Foundation.
Mono is not the answer you seek. Let’s start with text directly from the .NET Foundation project page: “It is most commonly used as a .NET runtime that supports the .NET desktop API profile, as well as an embeddable runtime that is used to power mobile platforms (Xamarin.iOS, Xamarin.Android, Xamarin.tvOS, Xamarin.Mac, Xamarin.watchOS) and gaming consoles.” So, maybe, if you are looking to port a desktop .NET app to Linux, Mono may be of use to you. But for real server workloads, Mono will not get you there. And even if it does, you’ll be facing (1) performance issues (.NET Core is much faster than Mono) and (2) non-commercial support. Mono is only supported by the community, so there is really no one for you to call when you face a critical issue in production. .NET Core, on the other hand, is commercially supported by Microsoft on Windows and Red Hat on Red Hat Enterprise Linux. The future of Mono is mainly to provide a client-side .NET framework for iOS and Android. Lastly, much of Mono is being reimplemented on top of .NET Core (well, .NET Standard, but I’ll get to that in a moment) — so you’ll need to get to .NET Core, anyway.
That’s the story, as of today (early December 2016).
What about the future? Well, let’s revisit some of the points I made above and promised to discuss in more detail.
Let’s start with naming and version numbers. In .NET Core, it’s a complete and utter mess. It’s very, very confusing. There are tools; there are frameworks; there are execution engines; etc. And they all have different and seemingly conflicting version numbers.
The good news is that this mess is recognized and acknowledged. As both a close Microsoft partner and a member of the .NET Foundation Technical Steering Group, we (Red Hat) are doing our part to rationalize the version numbers in the .NET universe.
The first attempt to address the mess was the creation of “.NET Standard.” The purpose of .NET Standard is to define a set of APIs which are available broadly across all implementations of .NET. The value of this is to make it easier to write portable code, but, really, in this context, it’s about writing portable libraries which can be used on all .NET platforms. (for instance, I write an encryption library and I want any .NET developer to be able to use it.) It’s really not about writing portable applications, especially visual ones. For example, .NET Standard does not make it possible for me to write a GUI application which runs as-is on Linux, Windows, Xbox, iOS, etc.
Today, we have implementations of .NET Standard 1.6. This is a set of APIs which are implemented in .NET Core 1.0/1.1. Now let’s be careful with some language. .NET Standard is a specification. .NET Core is an implementation. Furthermore, note that both .NET Core 1.0 and .NET Core 1.1 both fully implement .NET Standard 1.6. Yet .NET Core 1.1 has more APIs than .NET Core 1.0. How can that be? It’s because .NET Core implements a superset of .NET Standard 1.6. So, yes, it is possible to write .NET Core code which is not compatible with .NET Standard 1.6.
But that’s somewhat of a moot point, because, today, there are no other implementations of .NET Standard 1.6.
“Huh? How does this help with ‘porting’ code?”
There are two answers to that question. First, because .NET Core implements .NET Standard 1.6, that means it also implements .NET Standard 1.5, 1.4, 1.3, etc. .NET Framework 4.6 also implements .NET Standard 1.3. As such, if someone writes code against the .NET Standard 1.3 specification, it will work on both .NET Core 1.x and .NET Framework 4.6. But, as a reminder, in both cases the implementations are supersets of the specification. So while all .NET Standard 1.3 code is compatible with .NET Framework 4.6, not all .NET Framework 4.6 code is compatible with .NET Standard 1.3. (In fact, I’d say that most of it is not).
So what APIs are in .NET Standard 1.3? Well, honestly, it’s very hard to tell from the documentation. It may take you awhile to wrap your head around this, but, for me, it’s the easiest way to answer that question.
Which leads us to .NET Standard 2.0.
This is how porting, theoretically, is going to get much better.
You’ll want to bookmark this .NET Standard FAQ.
Let me start by correcting one misconception about .NET Standard 2.0. 2.0 is a full superset of 1.6. You may have heard that it is not. The reason you heard that is because, for a while, it was not. That was the original plan (and for good reasons), but Microsoft listened to the community feedback and changed their stance. So all existing .NET Standard 1.6 compatible code written today will work when .NET Standard 2.0 is finalized and gets implemented.
What will implement .NET Standard 2.0?
Remember, .NET Standard is just a specification. You can see the implementation plan here. Of note, .NET Framework 4.6.1 will implement it, as will .NET Core ”vNext.” (In other words, some future version of .NET Core.)
You want to know when that will be, right? Well, I’ll get there in a moment.
Let’s start with ‘what’s in .NET Standard 2.0?’ This specification has 32,638 APIs. That’s far more than double the 13,501 in .NET Standard 1.6. This should go a long way towards making porting efforts much easier. The vast majority of these APIs come directly from .NET Framework 4.6.1.
Of course, Windows-specific APIs (e.g. dealing with the Windows registry) are not part of .NET Standard. It is this very specific platform code which will require the most effort during the porting process. (And this has been true ever since there was more than one computer on the planet…)
Moreover, .NET Standard 2.0 has some APIs which will not work on .NET Core vNext. The classic example is the AppDomain class. A fair amount of existing .NET Framework code uses AppDomain. And the stated rule for .NET Standard is that if a class is brought from .NET Framework into .NET Standard, then the entire set of APIs for that class has to come. But some AppDomain functionality simply cannot work in .NET Core because .NET Core has a different application domain model than .NET Framework. It also turns out that the vast majority of real-world uses of the AppDomain class in .NET Framework code do not make use of those particular, .NET Framework-specific APIs. One solution that was considered was to refactor the AppDomain class to separate the .NET Framework specific APIs from the more generic APIs, but, given the rule of “bring them all or bring none,” the behavior you’ll see is this: if the API cannot work on the platform, a PlatformNotSupportedException will be thrown. In fact, this is not even new; the same pattern exists in .NET Core 1.0/1.1 today on about 100 APIs.
This will be a bit of a ‘gotcha’ for some code; it will run fine on Windows under .NET Framework, but it will throw exceptions under .NET Core on Linux. This is also anticipated to be a fairly rare thing. It’s worth noting that, under .NET Core when run on Windows, these same APIs will throw the same not implemented exception.
There’s some other magic being introduced in .NET Standard around supporting third party libraries compiled against .NET Framework — in other words, you’ve got some binary DLL without the source and you want to use it with .NET Core. Some of these will, in fact, work, with .NET Core, even on Linux. I suspect more than a few of them any of them will not, even though those same ones may work with .NET Core on Windows. It really comes down to what they do (e.g. do they do Windows-specific things, like reading the Windows registry) and does an ‘extension’ exist which implements that functionality. Theoretically, someone could implement a .NET Standard 2.0-compatible Registry class library on Linux, permitting all existing binary .NET Framework class libraries which required the registry to, as if by magic, also work on Linux with .NET Core. That’s pretty powerful (if not a little scary). It’s worth noting that Mono does implement cross-platform registry APIs, so I would anticipate someone making use of these to solve this very particular problem.
“So when do I get .NET Standard 2.0 and this porting nirvana?”
I don’t know. The .NET Core team likes to ship quality releases, and it’s very difficult to plan for/time box quality-based releases. The official roadmap, as of this writing, says “Spring 2017.” Mind you, that roadmap has not had a good track record of hitting release dates. If you asked me, completely off-the-record and over a beer, I’d probably guess that we’ll see .NET Core ”vNext,” on Red Hat Enterprise Linux, in July 2017, but don’t hold me to it. And it could be sooner.
“And what .NET Core version number will it be?”
Well, the roadmap is calling it “1.2” as I write this. As in “.NET Core 1.2 implements .NET Standard 2.0.” Like I said way back at the beginning, the myriad of version numbers for the entire .NET Core family of technologies is a big mess. Many of us are encouraging the team to unify it around one, simplified numbering scheme. I’d love to see .NET Standard 2.0 implemented in .NET Core 2.0, with all of the .NET Core tools, compilers, frameworks, etc., also be labeled 2.0. In this simplified place, it would all just be 2.0.
No promises, though.
For additional information and articles on .NET Core visit our .NET Core web page for more on this topic.