Deploy a .NET Desktop App on macOS

Deploy a .NET Desktop App on macOS

Developing an application and running it from an IDE (Integrated Development Environment) is one thing; distributing it to users is a whole other level of complexity.

This post approaches this complexity by exploring one specific case, divided into manageable steps.

The starting point is a "Hello World"-like .NET 8 desktop application. The end point is the same application packaged as a macOS app with an icon and a database.

Notably, this post does not cover the app signing process nor distributing the app beyond one's own computer.

The source code is available on GitHub here.

Preliminaries

A modern framework choice for developing desktop applications in .NET is AvaloniaUI.

The templates can be installed with the following command:

dotnet new install Avalonia.Templates

The templates' availability can be checked with the dotnet new list --author Avalonia command, producing output such as:

Template Name                        Short Name                 Language  Tags                                     
-----------------------------------  -------------------------  --------  -----------------------------------------
Avalonia .NET App                    avalonia.app               [C#],F#   Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia .NET MVVM App               avalonia.mvvm              [C#],F#   Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia Cross Platform Application  avalonia.xplat             [C#],F#   Desktop/Xaml/Avalonia/Browser/Mobile     
Avalonia Resource Dictionary         avalonia.resource                    Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia Styles                      avalonia.styles                      Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia TemplatedControl            avalonia.templatedcontrol  [C#],F#   Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia UserControl                 avalonia.usercontrol       [C#],F#   Desktop/Xaml/Avalonia/Windows/Linux/macOS
Avalonia Window                      avalonia.window            [C#],F#   Desktop/Xaml/Avalonia/Windows/Linux/macOS

With the templates in place, the project can be created by running the following commands, which create a solution, a project, and add the project to the solution:

dotnet new sln --name MacosDeployDemo --output macos-deploy-demo
cd macos-deploy-demo
dotnet new avalonia.mvvm --name MacosDeployDemo.UI
dotnet sln add MacosDeployDemo.UI

This commit shows the result.

Running the app from an IDE results in this window:

Warm Up

The .NET CLI command to prepare an application for deployment is dotnet publish, with documentation here.

The output can be found at ./MacosDeployDemo.UI/bin/Release/net8.0/publish/.

The app can be launched using the ./MacosDeployDemo.UI/bin/Release/net8.0/publish/MacosDeployDemo.UIcommand. This step ensures the application operates as intended when executed independently of the IDE.

Customised Publish

The publish command for later use can be slightly customised as follows: dotnet publish --runtime osx-arm64 --configuration Release --self-contained

  • --runtime osx-arm64 targets macOS with an ARM processor, also referred to as Apple M series, Apple Silicon, or AArch64. More information on runtime identifiers can be found here.
  • --configuration Release builds in Release mode for optimised performance. This is the default behaviour for .NET version 8 and above but is included here for clarity.
  • --self-contained packages the runtime, making it executable on target systems without additional dependencies.

Icon

An icon visually represents the application and should have the .icns extension.

An image editor such as Canva can be used to create an icon. Canva also provides icon templates with the required 1024x1024 dimensions and allows .png file downloads.

A tool like CloudConvert can be used to convert this .png to an .icns file.

This commit adds the prepared icon to the assets folder.

Bundling

MacOS applications have the extension .app. This is actually a directory rather than a single file.

Using Finder, its contents can be examined by right-clicking and selecting "Show Package Contents":

Here is the structure of the .app directory:

MyApp.app
|
--Contents\
    |
    --MacOS\ (the output of "dotnet publish")
    |     |
    |     --MyApp
    --Resources\
    |     |
    |     --MyApp.icns (icon file)
    --Info.plist (application metadata)

Creating such a directory is called bundling.

When bundling the app for the first time, it is a good exercise to do all the steps manually: create directories, copy the icon, copy the executables, and prepare the Info.plist file. Since this is tedious, this commit automates the process with a shell script.

Everything is ready: running the script with the sh deploy.sh command and double-clicking HelloWorld.app launches the app.

Updates are supported too: modifying the greeting (this commit), re-running the script, and launching the app results in a new greeting:

Data Persistence

Thus far, the app is stateless, meaning it does not save data across sessions. This commit introduces data persistence using EF-Core and SQLite. It does nothing more than creating a database when the app starts.

The app works when launched from the IDE, but nothing happens on double-clicking HelloWorld.app.

The problem lies with relative paths. When debugging, the data.db file is created in the ./MacosDeployDemo.UI/bin/Debug/net8.0/ folder, but when launching the bundled app, it does not work well with relative paths.

The convention is to save data to a special folder /Users/Username/Library/Application Support/AppName. As a consequence, all copies of the app, wherever they reside, share this data. Moreover, deleting and reinstalling the app does not affect the data.

This commit fixes the problem by setting the location of the database file to this special folder. More information about special folders support in .NET can be found here.

The app now runs successfully. The app data folder within the application support folder contains the data.db file. The app can be copied to the applications folder:

Next Steps

There are at least two pathways from here. The first is to apply the deployment process to apps beyond this demo app. The second is to understand the app distribution process beyond one's own computer.

Read more