For over a decade .NET Framework was the main platform that Microsoft stack developers could use to write their code. And during all those years, the platform evolved into a feature-rich set of tools, suitable for building any type of application imaginable.
As great as .NET Framework is, it has one major disadvantage—it was designed specifically for Windows. This limitation has, pretty much, restricted its usage to building apps for purely commercial and industrial purposes, as most medium and large companies use Windows-based workstations and host their intranet applications on Windows Server. This is because Windows is reliable and widely used, with a range of good support options available.
.NET Framework cannot be easily used for consumer-grade cross-platform applications due to its inability to natively run on Linux or Mac. Likewise, it is not the best choice of platform to build websites on. Although it comes with all the tools necessary to build websites, the costs of hosting a website on Windows server is usually prohibitive, especially as most of the hosting providers out there offer Linux-based hosting for a fraction of the cost.
Microsoft overcame these problem by releasing a new cross-platform cousin of .NET Framework that the company called .NET Core.
.NET Core Vs .NET Framework
There are many similarities between the two. They use the same programming languages. Also, many things have improved in .NET Core based on lessons learned from the long gradual evolution of its predecessor.
Unfortunately, .NET Core lacks many commonly used features found in .NET Framework. Examples of these include the inability of .NET Core to play audio and to perform certain graphical operations. This is because such features are implemented in completely different ways on different operating systems.
However, even though these features are absent from the platform out of the box, it is not impossible to write your own implementation of them.
In this article, I will specifically focus on how to enable the ability to play audio and the ability to build native desktop applications with a graphical user interface. However, some of the techniques that are described here will be equally applicable to any other features that .NET Core currently lacks.
The Difficulty With Cross-Platform Audio Playback
While .NET Framework comes with a variety of libraries that can play audio natively on your Windows machine, .NET Core comes with none.
This comes from the fact that audio is architectured completely differently on different operating systems. While varying modern versions of Windows use the same standardized set of international APIs for audio playback, different distributions of Linux may have some significant differences.
Although ALSA is the de-facto standard audio architecture on Linux, it is not guaranteed to be present in any given distribution. Even where it is present, it is not guaranteed to be configured in the same way on all Linux devices. For example, a Bash command that would reliably play the content of an audio file via an analog audio jack on one device will, at best, do nothing on another device or, at worst, simply return an error.
This is probably one of the key reasons why Microsoft developers have decided to leave this feature out of .NET Core.
NodeServices to the Rescue
The easiest way to implement the audio playback feature is to get some help from another popular programming technology.
.NET Core is not the only OS-independent programming platform out there. The most popular among those is Node.js.
Node.js has a list of its own limitations that don’t exist in .NET Core. And, just like .NET Core, the base installation only contains the most basic functionality that allows the platform to be usable. Any other functionality is added by custom packages via Node Package Manager (NPM), which is the most popular repository of software components compared to all equivalent repositories that exist on other platforms.
Of course, .NET Core has an equivalent repository of third-party components known as NuGet. However, as the platform is relatively young, there aren’t that many packages on it.
Node.js, on the other hand, is significantly older. Therefore, it has many packages available, covering pretty much any functionality you can think of.
Microsoft developers realized this. That is why they created NodeServices, a library for .NET Core that allows compiled code to interoperate with Node.js applications.
There are several audio-related packages already available on NPM. And, with the help of NodeServices, any of them can be used directly from your .NET Core code.
Going into the details of how NodeServices works is beyond the scope of this article, as many pages have been published on this topic. For example, this page provides instructions on how to use the play-sound NPM package in this context.
One caveat of using NodeServices is that it may not necessarily be the most efficient solution. The library depends on ASP.NET Core components, which you would normally only use for building web-hosted applications. If your application is only intended to run on a particular machine and not be hosted on the network, you will have those fairly sizable files in your application for no other reason than to enable interoperability with Node.js.
Also, your code will only run on a machine or inside a container that has Node.js installed. This is more computational overhead. Therefore, this solution will not be suitable for all situations.
Writing Your Own Audio Library
Luckily, the process of writing your own code and compiling it into an audio library without any third-party dependencies is not that complicated.
One such library, NetCoreAudio, which I wrote, has already been made available on nuget.org. It is fairly basic and only contains the most fundamental audio-player actions. However, it is open source; therefore, its code can be used as an example for writing more complex libraries.
The principle behind it is simple and should be understood by anyone familiar with object-oriented programming within the context of C# language. If not, the official Microsoft reference documentation explains the concepts of “interface” and “implementation” used below.
There is a public interface that exposes all available player actions including play, pause, stop, and resume. As .NET Core is made to work on three separate operating systems—Windows, Mac, and Linux—each of the operating systems has its own implementation of this interface. The root class, i.e., the entry point of the library that is directly accessed by the code that calls the library, determines which OS the application is running on and chooses the interface implementation accordingly.
Inside this particular library, Windows implementation uses Media Control Interface (MCI) by making direct calls to a specific assembly, which exists in any standard version of Windows operating system.
Linux and Mac implementations use a different approach. As both of these operating systems are based on an older operating system known as Unix, both implementations use Process class. This class is used by C# to call any external program in the background. In this case, it launches OS-specific audio utilities via Unix Bash, a standard command line utility present on any Unix-based operating system. Pausing and resuming the audio is done by suspending and resuming the process via “kill” commands with “-STOP” and “-CONT” flags accordingly. The usage of these flags is described in more details in this article.
This is how the library works in a nutshell. There is also a tutorial available that explains the principles behind the library in more detail.
Enabling a GUI on Any OS
We have covered audio playback. However, there is another important feature that is missing from .NET Core that users and developers alike expect to be in any installed application—a graphical user interface.
Any native app, whether it is a mobile app or an application installed on a desktop computer, has a GUI. Of course, it is possible to interact with native applications via textual input and output, and many developers do. However, this is now what the vast majority of users will want. Sadly, this is what .NET Core lacks.
.NET Framework has at least three different ways of creating native apps with a GUI on Windows: Windows Forms, Windows Presentation Foundation, and Universal Windows Platform. However, each of these contains a hint of why none of them can be used on .NET Core in an OS-independent manner: They are all made specifically for Windows.
Because of this, those technologies could not just have been ported over into .NET Core. And, as windowed GUI applications depend on OS-specific APIs, .NET Core doesn’t have any inherent capabilities for building native applications with a GUI.
In its most basic configuration, you can only build console apps. When you add ASP.NET libraries, you can build websites or web applications, but the GUI for those will only be accessible via a browser. There aren’t any other types of application templates available on the platform.
However, there is a way of adding a GUI to your .NET Core app. And the solution, once again, comes from Node.js, although more indirectly.
There are some popular desktop applications that were built using this technology. A client for Slack, a very widely used group chat application, is one of the most noteworthy examples.
However, Electron.js cannot be accessed by .NET Core code as easily as pure Node.js can. So, NodeServices library is of no use here.
Fortunately, a third-party library has been created to solve this problem. It is called Electron.NET and is available as a NuGet package.
The library does depend on ASP.NET Core components. This is because Electron.js app technically is a web application that doesn’t just happen to be hosted as one. It also runs inside its own window rather than a browser.
So, with Electron.NET, you will be creating a standard Model View Controller (MVC) web application with some additional components registered in the middleware pipeline. The MVC application template will be familiar to anyone who uses ASP.NET Core. However, you won’t need any server software to host it on. Once the application is compiled, you will be able to open it directly from the location of your compiled code.
The code for the library is open source, and the documentation on its GitHub repository contains usage instructions, all of which make using it incredibly simple. However, the application code isn’t simple. Therefore, you should not attempt to modify it unless you have at least intermediate programming skills and are reasonably familiar with Electron.js.
Making the Most of .NET Core
.NET Core is a great platform, and its code can run on any OS. The only downside of it is that it is relatively fresh, and as such, it doesn’t have as many libraries as more mature programming platforms. Because of this, there is a chance that certain business-critical functionality may be missing from it.
However, this is something that the developers of .NET Core anticipated. They created NodeServices in order to leverage Node.js, a much more mature OS-independent platform.
We have already seen how this library can be used to play audio inside of a .NET Core app via Node.js code. But this is obviously not the limits of the library. Any other functionality that is missing from Node.js but is available on NPM can be utilized by it.
Although this is the easiest way of adding any new functionality into .NET Core, it is not the most efficient way. C# itself is sufficient to gain access to any OS-specific components; therefore, you can write your own lightweight libraries to add any functionality you want to have in your app. We have already seen how you can use this approach to write your own audio playback library.
Identifying any other common functionality missing from .NET Core and writing a library to enable it could be a great side project for you that would help you to gain valuable experience and improve your reputation. Perhaps a library you wrote will enable you or someone else to build great software that solves an important problem.