Creating a cross platform desktop app in .NET Core (Part One)Packages for several targets
A windows developer’s journey to Linux
Days are gone when .NET was only a Windows thing. The only thing that keep .NET windows is not technical but cultural: The majority of .NET devs have always been windows centric, and you don’t change a culture fast.
.NET Core is more than a good cross platform environment, it is great one, one of the best choice you can make. The exception is for utility tooling: Either do that in bash or in Go. I think .NET will end up having a “no runtime” option in the future with a real single file deployment. (The current single file deployment is just a auto unzip behind the scene and weight around 30 MB)
Until then, bash and Go are cooler for utility tooling. For all the rest there is .NET Core.
And I am not saying this because my 12 years of professional .NET development is biasing my judgement. Since I started working in the Bitcoin world 4 years old, a linux centric environment, I experienced the great, the bad and ugly and could compare it to my old experience.
In a nutshell, my experience is the following:
- Windows Server will die. Not tomorrow, not in 10 years, but eventually. It will fade away with time and takes decades to disappear, but it will. It will never catch up Linux as the environment of choice for running servers. Since I stopped using windows server for hosting things, I could spend most of my time developing and cut down my servers cost by 10. Nano server is a niche.
- Bash is cool. And I am not subject to the Stockholm syndrome, it really is. It get simple things done fast. What is not cool is that the same bash script might not work in different environments, wasting your time on “it works on my machine” debugging nightmare and eventually make your script unreadable. Bash scripts are like wooden sticks holding together by gravity, move it somewhere else and everything fall apart. And this is where docker save the day, if you need to use your script somewhere else, always run it inside a docker image!
- Docker will save you lifetimes of debugging “it works on my machine” is a thing of the past. If your bash script worked once, it will work forever in the container in a reproducible way.
- .NET Core has the best development tooling quality, no competitors are even near.
- Windows desktop is great, the rest is not that bad, but Windows desktop is really great, especially since WSL. (Windows Subsystem for Linux)
- Windows Subsystem for Linux (WSL) can replace a dedicated Linux desktop for your day to day needs. It is really a Linux running on your Windows that just work.
So, back to the subject. How to make desktop apps, cross platform in .NET and more importantly, how to ship it. The packaging part took me more than 50% of the development time of the project, but now I tell you my secrets, I did it so you don’t have to.
But first let’s start, which UI technology to pick?
Creating a desktop app means that first you have to pick your tech, I tested some of them settled on one. Here is my experience.
ASP.NET Core Web App
Who said that web apps were only for servers? A perfectly fine approach is to run a ASP.NET Core Web App, and just open the browser when it starts.
Opening the browser in cross platform way is not that hard (you can google it), the stackoverflow responses just work and that will probably be the only platform specific code in your project.
The upside is of course you have no third party dependency hell, lot’s of doc, and everything you will learn there will be reusable for server projects.
Because the front end is in HTML/CSS/JS, it is very easy to find people contributing to your project and make hiring skills very easy.
The resources taken are very low on RAM and CPU.
The downside is that the result does not feel “native”. When the app open, a new browser tab is opening, which is not so natural experience.
ASP.NET Core Web App + Electron
There is several way of fixing the problem of the previous downside. One of them is using Electron.
Electron is nothing more but a chromium browser bundled into the app.
I know one project that did exactly this Breeze Wallet. As far as I know they organized their project it two part, one was the front end in typescript and the other project was the backend in C#. I think this separation made it easier to have the front end developers working independently from the back end developers. The result was looking really nice. I can’t go more technically on their setups as I did not worked in this project.
The downside is that electron apps are well known to be resource hungry. (looking at you, horrible slack)
ASP.NET Core Web App + WebView
A different approach I saw was, instead of using Electron, why not using the native libraries of the OS which can already render HTML?
Then I saw Steve Sanderson,Developer/architect at Microsoft on the Core team. Creator of Blazor, created WebWindow.
I gave it a try, but sadly it instantly crashed with cryptic error message. Steve informed me that this requires the new Chromium-based Edge via webview2, which is not yet released. Until I have an easy way to deploy on my user’s machine, I give up on the project. Might revisit in 5 years when Chromium based Edge is widely deployed.
In summary, this is not ready yet, but very promising.
There is a widely successful project using AvaloniaUI in the Bitcoin world: Wasabi Wallet. Knowing Avalonia might have rough edges, the creator of Wasabi wallet, Adam Fiscor hired Dan Walmsley a contributor in Avalonia to assist him building Wasabi Wallet.
Because Wasabi Wallet is widely successful, I considered trying it. My project BTCPayServer Vault, does not requires complex UX so I thought it was a good fix to dip my toes.
AvaloniaUI is very close to WPF. As far as the developer experience is concerned, Avalonia is what WPF would be if the creator could have restarted it from scratch. Having worked a lot on WPF in the past, it was very natural for me to use.
My simple project required a fixed size windows which can resize to show a small popup. It turns out to have been rigged by bugs. (#3290 #3291 #3293)
Granted, it is very hard to make a cross platform UI framework, and granted, what I did is slightly unusual. On the bright side, this project is heavily maintained. I got responses quick, and I could find a workaround good enough for my very small project.
The design experience is great. When I was working on ASP.NET Core, I really missed the instant feedback provided by a good preview designer integrated to Visual Studio.
With Avalonia UI, like in WPF, you have a nice preview and can easily tweak your XAML and get instant feedback. Doing the same in ASP.NET Core MVC is possible, but still it is not integrated to the IDE and you need to be running the app. I don’t think there is a designer for visual studio code, this basically mean that nobody not using Windows will be able to help you on the UI.
In my opinion, not having a fast development feedback loop is a deal killer for a UI framework, I threw Xamarin in a fire because of this in the past. So don’t expect tweaking the XAML on Linux or Mac OS.
Another good side of Avalonia: Hitting F5 to debug is FAST. I hate that ASP.NET Core need to load many assembly and sometimes having the razor compiler run for 10 sec before I can debug. This does not happen with Avalonia.
Now the downside is that it is of course harder to find any designer willing to contribute to the project. XAML is far from being spread as much as HTML.
The documentation is far from being optimal. Luckily, I could surf Wasabi Wallet code when I was stuck or only read Avalonia’s source code.
Next… packaging and deployment
I thought doing only one blog post, but your attention span is limited, as is my focus while writing for long time.
I eventually chose Avalonia UI for my project. But whichever you chose, you will have to distribute your app to desktop users (debian, Windows, MacOS) at least.
In the next blog post, I will talk about how I created and tested those packages, hopefully you will learn useful tricks and don’t have to search for ages to solve the problems I had along the way.