How to build cross-platform console apps with .NET Core

Now you can write your C#/.NET code once, on any platform, and run it on Windows, Linux, and macOS.
524 readers like this
524 readers like this
How to build cross-platform console apps with .NET Core

Opensource.com. CC BY-SA 4.0

Although .NET has traditionally been a Windows-only, closed-source proprietary platform, those days are coming to an end. The new .NET Core platform is here and it's open source and cross-platform. You can now write your C#/.NET code once, on any platform, and run it on Windows, Linux, and macOS.

This new .NET platform is being built in the open on GitHub, mostly using MIT and Apache licenses, and even accepting community contributions. This new platform has been designed from the ground up around the previously open-sourced Roslyn compiler (.NET Compiler Platform), and has been designed not to be tied to Windows or Visual Studio, allowing developers to work on any system in any IDE, and to run their apps on any server or platform.

To demonstrate this new platform, let's build a super-simple console app that we can run on Windows and Linux using the new dotnet command-line interface (CLI). To get started, you need to install .NET Core (which includes the dotnet CLI tool). Microsoft provides complete documentation for most platforms (including a wide variety of distributions) in their online documentation.

If you don't want to install .NET Core locally, the microsoft/dotnet:latest Docker image comes pre-loaded with all the latest tools and the SDK.

1. Create our new app

.NET Core is changing rapidly. These instructions are for the latest version at the time of writing.

First, we can create a new console app using dotnet new console. This will automatically create a Program.cs and a .csproj named after the current directory (mine is app.csproj).

dotnet new

To prove this works, try running dotnet restore, which will restore any packages needed to run your app. After that finishes, run dotnet run and you should see the all-important Hello, World! appear in your console.

dotnet restore run

2. Adding logic to our app

Open Program.cs in your favorite text editor and replace the line that reads Console.WriteLine("Hello World!"); with the line below. Because .NET Core is not coupled to Visual Studio, you can use any IDE or text editor you like, even vi.

Console.WriteLine($"Hello {System.Environment.GetEnvironmentVariable("USER")}! I'm {System.Environment.MachineName} and I'm talking to you from {System.IO.Directory.GetCurrentDirectory()}");

For those not familiar with C# and .NET, this code is essentially printing a more detailed version of the classic Hello, World app, now using information from the current environment. Note that there's no platform-specific code here, either.

3. Running your new app

If you now run dotnet build, you will get your .dll and other files added to your ./bin/Debug folder. You've already written a cross-platform console app. Copy those files onto another machine running .NET Core—even a Linux or OS X one—and run dotnet ./path/to/your/app.dll, and you should see the same output as before; however, you're still using your local .NET runtime to do that. Fortunately, .NET Core includes the ability to build and publish native binaries in a standalone package so that your target system (or users) don't even need to have .NET installed to run your app.

4. Preparing for other platforms

To prepare for a cross-platform app, we must tell the .NET Core SDK for which platforms to build. Because this process involves native libraries, we must be specific about not only which platform, but also specific flavors and versions.

Open your .csproj file from the app directory, and add the following XML tag somewhere between the
and tags:

<PropertyGroup>
    <RuntimeIdentifiers>win10-x64;osx.10.12-x64;debian.8-x64</RuntimeIdentifiers>
</PropertyGroup>

This tells .NET that we want to build a self-contained version of our app for Windows 10 64-bit, macOS Sierra, and Debian 8. Now all you need to do is run dotnet publish for each of the runtimes we chose earlier to compile native binaries for each platform:

dotnet publish screenshot

Look in the ./app/bin/Debug/netcoreapp1.1/ folder and there will be a directory for each of our platforms, each with a publish folder. In that folder is a copy of not just a native version of your app, but also all the .NET libraries needed to run, so you don't need to install .NET on your target system. Just copy the files onto a clean box and run the executable for the platform you're on.

file output screenshot

run output

Conclusion

Using C# and the new .NET Core platform, you can build an app on any platform, and publish it to any platform—100% code reuse, fully native, no runtime or framework installation required. You can see the new framework powering this technology on GitHub (and the runtime source is also on GitHub). These are exciting times for C# and the .NET platform as a whole.

Alistair Chapman
Alistair Chapman is an InfoSec Engineer, .NET developer and technical architect. While he's currently working at Red Hat, he's also done everything from network engineering to DevOps transformations. When not at work, Alistair is active in the .NET open-source community, including maintaining Cake (a .NET Foundation project). All opinions are my own, and do not represent Red Hat.

4 Comments

Creating a Windows app using root?

Well, you have failed to convince me.

The root user is simply the default for Microsoft's `dotnet` Docker image. Obviously, the app will run as whatever user you use to launch it, including non-root users. As you can see in the Windows screenshot, you don't need to be root.

The main goal was simply to demonstrate that you can now run .NET apps across any platform, including Docker!

In reply to by isnotmenow (not verified)

The problem with this article is that it does not answer "why" you would want to use .NET for this. The far more logical choice is clearly Go, which has the advantage of being natively compiled, with far less runtime complexity or overhead than what is required to transplant .NET to other environments. At best one might compare and contrast C#/.NET with Java (or maybe one day with Swift), but once you look at something like Go for this particular purpose, things like C#/.NET, Java, even Swift before it arrives, seem irrelevant, ineffective, and ancient worst case choices for the purpose the author lays out. Go does not solve all problems well, but this particular one it does particularly well.

I'm actually a big fan of Go, and it's approach to native compilation! I'm also a strong believer in choice and think it's great that developers now have more choice in the cross-platform space.

While I agree that Go has a great solution to cross-platform, Go and .NET are very different languages, platforms and ecosystems. C#, for example, is a very full-featured language with generics, exceptions, native async support, overloading, and nullable types; Go has none of these (to my knowledge).

Equally, the NuGet package ecosystem is a big part of the .NET platform, while Go (as a convention) tends to avoid dependencies as much as possible, and certainly doesn't bake in support for package management, like .NET Core does.

Conversely, working with low-level primitives (like pointers), some async constructs (using goroutines) and interface implementation (since it's ambient) are all vastly easier when developing in Go than .NET.

I certainly wouldn't want to suggest that .NET is the correct solution for every kind of developer, or every kind of use case, but wanted to demonstrate that developers now have an extra choice when it comes to standalone, cross-platform development.

.NET Core may be many things but it's certainly not "irrelevant, ineffective and ancient", it's just not the right solution for every use case.

Hope that helps!

In reply to by David Sugar (not verified)

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.