Modernising and cross-platforming a .NET Windows desktop app - Part 1
- 7 minutes read - 1285 wordsIn this blog series I will take an existing .NET Framework WinForms desktop application, modernise the codebase and make it work cross-platform.
The series will be in three parts and will allow you to cherry-pick the bits that might apply to your scenario; all applications are different, have different needs and are subject to different business pressures.
- Part 1 (this post): Why you might need to modernise and cross-platform an existing desktop application and how you can do the simplest (but wrong) thing.
- Part 2: Using my guinea-pig application, create a safety net of tests around the code before refactoring (where necessary) and moving from .NET Framework to .NET (Core).
- Part 3: Continuing the journey, I’ll replace the WinForms part of my guinea-pig application with a true cross-platform UI framework.
Why modernise an existing application?
There are many reasons why you might need to modernise an existing application:
- growing costs of maintaining a legacy codebase due to lack of vendor support
- security vulnerabilities in obsolete frameworks or libraries that are no longer maintained
- outdated architecture precluding the use of cloud-native services, leading to rising infrastructure costs
- outdated architecture that cannot scale to the increased loads of the present day
- outdated UI/UX or lack of mobile device support that cannot meet end-user expectations
- new regulatory compliance requirements that the current codebase cannot meet
- business agility that requires faster and shorter release cycles to maintain competitive advantage
- integration with other more modern services and/or data stores may be difficult in a legacy stack
- difficulties in retaining and/or recruiting staff to work with a legacy stack and the need to recruit for specialised skills
- take advantage of the latest technology and performance improvements from framework or library enhancements
Why cross-platform an existing desktop application?
Historically, Windows was ubiquitous at work and home. In the corporate world your Windows device was given to you by your employer, it was centrally managed, with the allowed software pre-installed and centrally configured. At home, Windows was also the predominate operating system. And if you went to the high street to buy a device, that was all that was on offer!
However, the world has changed; macOS is very popular and rising at home and the workplace, Linux as a desktop is on the rise too, not to mention mobile devices running iOS and Android. You can even BYOD to some workplaces!
What this means is that you can no longer assume your users run (or want ro run) on Windows. Your application needs to work across multiple operating systems and device form factors.
Lastly, any given user may use your application across different operating systems and devices depending on where they are, e.g. a laptop in the office and a mobile device whilst travelling. They want a modern UI/UX and they want your application to look and behave in a consistent way across those devices.
My guinea-pig application
To make this a bit more interesting, I’m not going to modernise a boring line of business application. Instead my starting point is a re-creation of a QLOCKTWO I developed several years ago as a screensaver (remember those?) called “TimeInWords”. Ultimately I plan to turn it into an appliance I can hang on the wall running on Linux on a Raspberry Pi, but I need to modernise and cross-platform first. My application is closer to a full-screen kiosk-type application, but the principles would still hold true for a “normal” business desktop application.
What exactly is a QLOCKTWO anyway?
I’m glad you asked! Simply put, it’s a clock that spells out the current time in words. The time is rounded down to five minute intervals with the four “dots” around the outside (clockwise, starting from top-left) denoting the four minutes in between each interval.
Shown below is my version running in debug mode to demonstrate it’s operation. In debug mode the time in words and digits are displayed top-left and the minutes are auto-incremented every second. In normal mode the application displays full-screen and displays the correct time ;-)
Do the simplest thing
What is the simplest thing to run my app on Linux/Raspberry Pi? Run it on Mono! Mono is a free and open-source re-creation of the .NET Framework that runs on Windows, macOS and Linux. It does have issues, which I’ll come to later, but doing this is a lost-cost, risk-free way of getting us going (and testing out my Raspberry Pi).
This blog isn’t really about setting up a Raspberry Pi from scratch (I’m using a 2GB model 4B with “Raspberry Pi OS with desktop” installed) so I’ll skip over that and start with installing Mono at the terminal:
sudo apt update
sudo apt upgrade
sudo apt install mono-complete
It’s as simple as that. And to test Mono is working run mono
at the terminal and it should print out a list of argument help text.
Cool, now let’s get my net481
TimeInWords application onto the Pi. I publish it as a “framework dependent” application in “Release”
configuration. But you can take whatever deployment artifacts you already have; no re-compilation is necessary to run on Mono.
To copy the files from my Windows development machine to the Pi I configured SSH on the Pi and used WinSCP.
Next I can run the application from the Pi terminal with mono TimeInWordsScreensaver.exe /s
(/s
means full-screen).
And this is what I get:
Pretty good for a first attempt!
Now your mileage will vary, depending on your application and what libraries it uses. To illustrate, and pick up on what I said earlier about Mono having “issues”, notice the cursor is still visible in the centre of the screen? After some Googling and research the issue comes down to this:
Nice! This is taken from Mono’s implementation of System.Windows.Forms
, the WinForms application framework that
TimeInWords uses to display it’s UI.
So what’s the problem with Mono?
As of today, many things with Mono are problematic for this type of endeavour:
- Mono was a re-implementation of .NET Framework and the future of the .NET platform is .NET Core (although the “Core” moniker has now been dropped)
- Mono’s last major release was in July 2019 and is no longer actively developed independently (although a fork lives on in the dotnet/runtime repo)
- Whilst Mono has very good coverage on the Base Class Library, many of the other frameworks bundled in .NET Framework are not well covered or not supported. Most relevant for a desktop application is no support for Windows Presentation Foundation (WPF)
- For TimeInWords, Mono’s WinForms implementation was not able to achieve full compatibility because
System.Windows.Forms
is mainly a wrapper around the Windows API and equivalents are not always available on other systems (for example, see the issue above) - Mono/WinForms on macOS is limited to the 32-bit Carbon API, which is deprecated, and as such 64-bit WinForms apps cannot run on 64-bit macOS
Whilst the development of Mono and what it has achieved has been undoubtedly amazing, the truth is that re-implementing a platform of the size of .NET, keeping pace with the new changes and supporting it going forward is a truly enormous task. As such, if I were modernising/cross-platforming a .NET Framework Windows desktop app for a client, I could not recommend using Mono.
So what next?
The approach I will take to modernise my TimeInWords application is to migrate to .NET (Core) to get back onto the Microsoft-supported .NET “mainline”. As well as being inherently cross-platform, I will gain all the latest language and framework features that come for free with the latest version of .NET.
After that has been completed I will find a cross-platform UI framework to replace WinForms and then I can truly run my app anywhere!
Check out part 2…