DEV Community

Cover image for Running a .NET 8+ App on Something Other Than Windows Part 1
Tomas Gallucci
Tomas Gallucci

Posted on Edited on

Running a .NET 8+ App on Something Other Than Windows Part 1

This series is a tutorial for a friend of mine. He has need to run code that was written against .NET 9 or 10 on something other than Windows.

While that code can be run naively on macOS or a Raspberry Pi, the goal is to show code running in a Docker container that pulls in a NuGet package.

In this article, we'll look at running .NET code natively on macOS; the process for running .NET on Linux or a Raspberry Pi should be very similar.

In the next post, we'll also discuss containerizing this project with a multi-stage Docker build.

Code for the final application as well as the multi-stage Dockerfile can be found on GitHub

Building "Hello World!" in C# against .NET 10 on macOS

For our first trick, we'll get the classic "Hello World!" in C# working macOS Golden Gate 27.21.

Installing .NET

First, we need to download and install .NET2. The official download site can be found here, but .NET is also available via Homebrew3.

Once .NET is installed, and your environment has been updated4, you should have the dotnet command available to you, try

dotnet --version
Enter fullscreen mode Exit fullscreen mode

to prove your shell is set up and ready to go.

Building "Hello World!"

Run the following command:

dotnet new console -n HelloWorld
Enter fullscreen mode Exit fullscreen mode

This will create a directory called HelloWorld which will contain a Program.cs file, an obj directory with object files inside it and a HelloWorld.csproject file.

The entirety of the Program.cs file is a single line:

Console.WriteLine("Hello, World!");
Enter fullscreen mode Exit fullscreen mode

To build the project, run the following command:

dotnet build
Enter fullscreen mode Exit fullscreen mode

Running "Hello World!"

To run the program, cd into bin/Debug/net10.0

cd bin/Debug/net10.0
Enter fullscreen mode Exit fullscreen mode

and then execute

dotnet HelloWorld.dll
Enter fullscreen mode Exit fullscreen mode

From the screenshot above, you may have deduced that HelloWorld is an executable file because its name is in red. In fact, running file on HelloWorld tells us HelloWorld is indeed a Mach-O 64 bit executable arm file:

And you can execute it:

Running file on HelloWorld.dll gives us PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows5.

It turns out that the Microsoft preferred way of executing .NET applications is to execute the .dll, not call the executable directly.

Building a .NET application with a NuGet package dependency

First, we'll replace the single line of code with the following:

using System;
using Newtonsoft.Json;

// Your original Hello World
Console.WriteLine("Hello World!");

// New JSON code
var data = new { message = "Hello World!" };
string json = JsonConvert.SerializeObject(data, Formatting.Indented);

Console.WriteLine(json);
Enter fullscreen mode Exit fullscreen mode

To build and run the project with one command execute the following:

dotnet run
Enter fullscreen mode Exit fullscreen mode

Oh noes! We have an error!

Adding a NuGet package to the project from the command line

Actually, that was intentional; I wanted to demonstrate what it looks like when you don't add the NuGet package to the project. Execute the following command to add the Newtonsoft.Json NuGet package to the project:

dotnet add package Newtonsoft.Json
Enter fullscreen mode Exit fullscreen mode

Now dotnet run will build and execute our .NET program:

Not very exciting, but it works.

In Part 2, we'll discuss a multi-stage Docker build for this project.


This is a value for value blog. If you got any value out of this post, please translate it to a number and send it back to me:


  1. I assume that what I'm about to show will work on other, older versions of macOS, but that was the version of macOS I was running when I attempted this feat prior to documenting and it's the only version of macOS that I have access to. ↩

  2. My friend and I were having trouble communicating versions of ".NET" early on thanks to Microsoft's very not-clear naming convention. .Net Framework was the original name. Then came .NET Core where Microsoft attempted to standardize .NET and make it cross platform for Windows, OS X and Linux. Staring with version 8, the nomenclature is now just ".NET" or ".NET 8", ".NET 9" or ".NET 10". ↩

  3. .NET is also available via Homebrew for (Raspberry) Pi OS. Microsoft recommends using their install script by piping it right into your command line a la curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel LTS; of course that's a very, VERY safe operation. (Please note the dripping sarcasm.) ↩

  4. You may have to exec your shell for any open tabs to get the shell to re-read your .*rc file e.g. .zshrc Example command: exec zsh. ↩

  5. Keep in mind that this file doesn't really execute; it's the .NET interrupter that executes. What's really inside the .dll is Common Intermediate Language (CIL). If a 64-bit environment isn't explicitly targeted, the default wrapper for CIL inside a .dll for Any CPU is PE32 NOT PE32+. ↩

Top comments (2)

Collapse
 
alexshev profile image
Alex Shev •

The walkthrough makes the native path concrete before introducing the container layer, which is exactly the right sequencing. For the Docker follow-up, it would be useful to show how restore caching changes when the project file or NuGet source changes, and to include one command that proves the image behaves the same on both Apple Silicon and a Linux host.

Collapse
 
professortom profile image
Tomas Gallucci •