In this Article

When people talk about WebAssembly and .NET Core these days, they immediately think about Blazor WebAssembly. But there is more to it. Actually, Blazor WebAssembly currently does not run our code as a WebAssembly (WASM) module, but rather runs the .NET CLR (or a Mono version of it) as a WASM module and our .NET code is run as plain old .NET assemblies on top of it. But there is indeed the need for executing (native) code as WebAssembly modules in the browser, and in .NET Core applications as well. In this article, I show how you can achieve running WASM code in .NET Core, with a most likely future-proof approach.

The WebAssembly Universe: a Code Execution Platform

WebAssembly has been introduced into major browsers some years ago (beginning at the end of 2017). With it, you can take more or less arbitrary source code and languages and compile it into a binary bytecode, usually ending in a .wasm file. People soon realized that it would be a great idea to not interpret WebAssembly as WebBrowswerAssembly :-). The notion of a common, very lightweight runtime that can uniformly run any code on any platform, is tempting. So, under the lead of Mozilla, the WebAssembly experts started to work on a specification that enables a uniform hosting, execution, and usage of WebAssembly also outside of the browser.

WASI

The WebAssembly system interface (WASI) was born. The perfect intro to WASI can be found in the GitHub repo of the Wasmtime project:

It is an API [...] that provides access to several operating-system-like features, including files and filesystems, Berkeley sockets, clocks, and random numbers, that we will be proposing for standardization. It is designed to be independent of browsers, so it does not depend on Web APIs or JS, and is not limited by the need to be compatible with JS. And it has integrated capability-based security, so it extends WebAssembly's characteristic sandboxing to include I/O.

That really sounds good and very promising to me. With WASI we can have a uniform interface for the browser and any other execution environments to run WebAssembly code. This indeed sounds a little like a potential base for having very lightweight sandboxes safely executing code - something that may be an interesting alternative to Docker, someday.

Wasmtime

The Wasmtime project from the Bytecode Alliance is an effort to create a WASI-compliant stand-alone runtime for WebAssembly: https://wasmtime.dev/. This means that we can run WASM modules that adhere to WASI everywhere, either via a CLI or via embedding it into other applications.

Currently, Wasmtime supports

  • Rust
  • C
  • Python
  • .NET
  • Go
  • Bash

There we have it: .NET (Core)!

A Simple Sample: Fibonacci, All the Things!

In order to get a clear picture of what is possible and also of what is needed to run WebAssembly code in a .NET Core application, let us create a sample application. Suppose, we have some really sophisticated C-based algorithms. They are proven, they are mature, they are blazingly fast. Indeed, we are dealing with this situation in customer projects from time to time.

However, I cannot expose those intellectual property assets in an online article. Therefore, let us use a C implementation of finding the n-th Fibonacci number for demonstration purposes.

Please have a look at the following quite simplistic implementation:

int fib(int n) {
  if(n <= 0){
    return 0;
  }

  int i, t, a = 0, b = 1;

  for (i = 1; i < n; i++) {
    t = a + b;
    a = b;
    b = t;
  }
  
  return b;
}

The beginning of the Fibonacci sequence is: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... If we pass a value of 4 as n into the function, the result will be 2. You get it :-)

Compiling C/C++ Code to WebAssembly With Emscripten

There are a number of compiler tools able to compile C or C++ code to WebAssembly. One of the most commonly used is Emscripten. Our C code above is simple enough for explaining the WebAssembly use case, and the only thing we need to do is decorate the function with the EMSCRIPTEN_KEEPALIVE macro.

#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int fib(int n) {
  if(n <= 0){
    return 0;
  }

  int i, t, a = 0, b = 1;

  for (i = 1; i < n; i++) {
    t = a + b;
    a = b;
    b = t;
  }
  
  return b;
}

This macro takes care that the function will not be inlined and will be exported for external usage.

If you want to avoid to repetitively install the latest Emscripten toolchain, you can save some time by using the official Docker image.

The following command runs emcc in Docker, passes the wasm/fibonacci.c file as an input parameter and generates the WebAssembly file in wasm/fibonacci.wasm:

docker run \
  --rm \
  -v `pwd`:`pwd` \
  -w `pwd` \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc native/fibonacci.c -o wasm/fibonacci.wasm --no-entry

The important detail here is to explicitly specify a .wasm file as the output and the --no-entry flag. With both in place, emcc generates a WASI-compliant interface and does not use Emscripten's proprietary ABI and system interface.

We can put this command into a shell script to re-use it in the build process. Let us use this now to create the needed .wasm file and host it in a .NET Core application - specifically in an Azure Function with C#.

Using WebAssembly Modules in .NET Core

One of the currently simplest and most straightforward ways of running WASM code in .NET is to use the wasmtime-dotnet .NET embedding of Wasmtime.

We can add it to our project simply by adding the Nuget package: dotnet add package --version 0.19.0-preview1 wasmtime (preview version at the time of writing)

With this in place, we can write a simple Azure function.

Creating an Azure Function to Execute WebAssembly Code

Our Azure Functions project from the demo repository looks like this:

Azure Functions with WebAssembly via Wasmtime

The essential files are:

  • the source C file in the native folder
  • the compile/build shell script containing the Docker emcc command
  • the resulting .wasm file in the wasm folder

All that is left is to use wasmtime-dotnet's API in the Azure Function to load the WASM module and invoke the desired C function via an HTTP trigger - fib(number) in our case:

public static class RunWasm
{
    [FunctionName("FibonacciWasm")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous,
           "GET", Route = "fibonacci/{number}")]
        HttpRequest request,
        int number,
        ILogger log)
    {
        using var engine = new Engine();
        using var module = Module.FromFile(engine, "wasm/fibonacci.wasm");

        using var host = new Host(engine);
        using dynamic instance = host.Instantiate(module);

        var result = instance.fib(number);

        return new OkObjectResult(result);
    }
}

wasmtime-dotnet uses dynamics which makes the API very readable.

If we finally call the Azure Function with a number parameter of, for instance, 12 (e.g. by simply calling the URL via a browser), it returns 144 as the result from invoking the WebAssembly code (remember: which was a C algorithm in the first place).

Azure Functions in Debugger

Voila!

Conclusion - Running WebAssembly Beyond the Browser

Having the ability to use WebAssembly as common byte code and WASI as a common interface to run WebAssembly code literally everywhere - and beyond the web browser - is very tempting. In this article, you have seen how to take WASI-compliant WebAssembly code, like existing C/C++ code and run it in your modern .NET Core application. A number of inspiring use cases are opening up - at least in the heads of some customers.

The entire sample application can be found on Github here.

Subscribe to our monthly Dev-News to receive our newsletter featuring the latest content by our experts.

Related Articles

blazor
Blazor WebAssembly - Unleash the Power of Dynamic Template-Based UIs With Razor Engine
In general, you can divide template engines into two types. The relatively simple ones are using template strings with placeholders to be replaced by some concrete values. The other template engines can do everything the simple ones can but additionally provide means for control…
Pawel Gerr
blazor
ASP.NET Core Blazor WebAssembly: Authentifizierung und Autorisierung mit IdentityServer in Aktion
Seit der Version Blazor WebAssembly 3.2.0 enthält Blazor umfangreiche Unterstützung für clientseitige Authentifizierung, wodurch die Implementierung von OpenID Connect und OAuth2 in Single-Page-Applications (SPAs) deutlich vereinfacht wird. In diesem Artikel sehen wir uns an, wie…
Patrick Jahr
blazor
Blazor WebAssembly - Changing the Log Level at Runtime
With Blazor WebAssembly we are now able to create single-page applications (SPA) using C# and the ASP.NET Core Framework. When coming from ASP.NET Core MVC, you may ask yourself what .NET features are available, limited, or not available when running in the browser. One of them…
Pawel Gerr
blazor
Blazor Components Deep Dive - Lifecycle Is Not Always Straightforward
When starting with new frameworks that have a lifecycle for their artifacts like components, then you may assume that the lifecycle is strictly linear. In other words, step A comes before step B comes before step C, and so on. Usually, this is the case until it is not. The…
Pawel Gerr