Developing .NET software on Linux with Mono
The obvious question here is why would you want to develop .NET software on Linux or for Linux? At the risk of sounding like throwing buzzwords around, I will say it is because Linux dominates the cloud completely. Many cloud-related technologies such as Docker, Mesos, and others build on Linux as a base. Sure, it is possible to run Windows in the cloud one way or another, but it is really hard to match the flexibility of Linux, especially when running more than just a few instances.
Quite recently Microsoft announced open-sourcing of .NET Core paving new grounds for a truly cross-platform .NET implementation. It has already been possible to run a lot of .NET software on Linux and OSX for quite some time on an independent .NET implementation called Mono, and now Microsoft is saying that they will work with the Mono project on a common code base that will eventually become the .NET core. In fact, Microsoft has been close to Xamarin, a company behind Mono, for a while now, so this step is not that surprising.
But how usable is Mono right now? That is what I set out to find out in my little experiment.
In a few words Mono is a
cross platform, open source .NET framework which supports
everything in .NET 4.5 except WPF, WWF, and with limited WCF and limited ASP.NET 4.5 async stack. It is important to note that Mono version numbers do not reflect Microsoft .NET versions in any way, so it is a good idea to take a look at the compatibility notes before starting.
Installing Mono on a modern Linux system is just a matter of typing a few commands and waiting for a few packages to download. On Debian, running the following command will give you the runtime, development toolchain, an embedded ASP.NET-compatible web server, and an IDE with a few useful plugins. This will get you started, although for a more permanent replacement for IIS, you will want to look into mod_mono for the popular Apache HTTP server.
sudo aptitude install mono-devel mono-xsp4 monodevelop monodevelop-nunit monodevelop-versioncontrol nuget
The development environment
Now that we are setup and good to go, we needed something to tinker with. I decided to dive into Triforks source code repository and fetch the first .NET project that crossed my mind. It can be opened directly inside MonoDevelop, which is the official IDE for the Mono project, and supports C#, F#, Visual Basic .NET, C/C++, and Vala. It can natively open SLN and CSProj files.
A small complaint I have is that MonoDevelop tends to reformat and rearrange the project files. Not a big deal, but it does make it harder to compare the files with the originals.
When opening a new project, MonoDevelop will attempt to download the required assemblies using the NuGet package manager. It may complain about an invalid certificate, because none are trusted in Mono by default. Certificates can be imported by running the following command:
mozroots --sync --import
After that it worked as expected for the most part. Although it did fail to download some of the packages because for some reason it could not resolve some transitive dependencies. If that happens, you will want run it manually. Telling it to download the transitive dependency explicitly with a specific version number did the trick:
nuget install jQuery -Version 2.1.3
And after a few hours your workspace may look something like this.
Building and running the project
Now that the assemblies are in place, clicking on Build, Build All in MonoDevelop yields the first interesting error:
The type or namespace name `AccountManagement’ does not exist in the namespace `System.DirectoryServices’. Are you missing an assembly reference?
Am I? No, it does not seem to be implemented in Mono. This is an example of Windows-specific functionality, and it would need to be rewritten. A quick look at the project code reveals that it is used for managing user groups. In my case this was not a crucial functionality, so I was able to replace the code with a simple mock.
Identifying such platform-specific functionality and listing possible solutions is a good starting point in considering if moving to another platform is at all worthwhile. The exact amount of work may vary greatly depending on alternatives available, how tightly it is integrated into the project, etc.
Luckily that was the only build error, and I could attempt to run the project. But before doing that, an essential difference in how Windows and Linux systems handle file systems has to be considered. If the software was developed on Windows, it is very likely to use Windows paths such as
C:\Directory\File.txt as opposed to what is expected on Linux such as
/directory/file.txt. This and case-sensitivity is the most common source of headache. Mono does have a work-around, which can be enabled by setting the environment variable MONO_IOMAP like this:
MONO_IOMAP=all mono myapp.exe
Although first make sure that it is really needed, because such magic solutions may cause problems as much as solve them, and you may end up with a confusing error message. Fixing the core of the problem inside the source code is often the more reliable and permanent solution. Do not rely on absolute paths, but rather use paths relative to where your application is deployed, whenever possible.
Other useful parameters are MONO_LOG_LEVEL=debug and MONO_LOG_MASK=asm, which will make Mono output detailed information about assemblies being loaded. It will come in handy when there is something wrong with the project dependencies. In my case it was a .NET 4.0 project trying to load a library built for .NET 4.5.
These can also be set up from within MonoDevelop inside Project Options.
One more important thing I will only mention briefly is a database (or data storage in general). If your project uses Microsoft SQL Server, get ready to get your hands dirty migrating your data elsewhere. Case-sensitivity will probably be your enemy number one once again. In addition to SQL statements, watch out for C# code like
something.ToUpper().Equals(somethingElse), which may potentially be a problem if
somethingElse is read from a database. You may want to do something like
something.ToUpper().Equals(somethingElse.ToUpper()) to make it consistent.
So is it realistic to deploy your .NET applications on Linux? I would say yes. Even though you have to be aware of the platform you are targeting, and some compatibility problems are to be expected. But with Microsoft backing Mono, it will only become easier and more reliable to run .NET software on alternative platforms. When the platform is no longer dictated by the implementation technology, it becomes a question of what serves the interest of the involved parties best? It becomes a motivated choice.