Utilize WebAssembly in .NET

Utilize WebAssembly in .NET

Utilize WebAssembly in .NET

We heard the WebAssembly quite a while ago but the use case, especially for .NET developers, was still limited. As of the time writing this post, in the last quarter of 2022, there are many new things around the WebAssembly itself yet the arrival of .NET 7 that comes with the interesting feature to work with the WebAssembly.

Due to most of the toolchains being open source, some are in the experiment phase hence the development is fast and very likely to break changes as it is getting reshaped to be more mature through time. So, I am writing this post to be more like a reference for myself and a seed for someone that is interested to explore the possibility of WebAssembly in the current and future for further research.

WebAssembly


Use the WebAssembly on the browser

The WebAssembly was originally designed to be used on the web browser (as its name state). There are many languages supported to implement and build the .wasm file with the help of extensions — Rust, AssemblyScript (Typescript-like), Emscripten (C/C++), .NET 7 (C#) and many more.

Once we have .wasm file then we can call it using the built-in WebAssembly library in most of the browsers using either WebAssembly.instantiateStreaming() or WebAssembly.instantiate().

The WebAssembly is function-based, and you can use it either.

  • Export functions from the .wasm file to the javascript. Then javascript can call those functions as if they are the regular javascript function.
  • Import functions of the javascript to the .wasm. Then the WebAssembly can call those functions inside. The same concept as function delegation.

You can find many useful hands-on samples in Wasm By Example. For .NET, further detail and some promising samples can be found in:

Support Types and Type Mapping

The WebAssembly, at this point, has still some limitations and the major problem for me is the type passing. Due to the WebAssembly being a binary format and can be compiled from various languages, passing complex types like a class can be a challenge. It depends on the .wasm compile tool to handle type mapping. Like in Rust, here are the supported types in wasm-bindgen. While .NET supported types can be found here, the types are automatically detected and mapped between JavaScript type and .NET type. You can also manually define JSMarshalAs attribute.

For passing the complex types like a class in .NET, there are two options. Run .NET from JavaScript | Microsoft Learn

  1. Pass json object from JavaScript and map to JSObject in .NET. JSObject type has GetPropertyAs[Type](“propertyname”) functions to retrieve property value.
  2. Serialize json object from one side and pass to the function as a string and deserialize it on the other side. I would recommend this option in most cases esp. for complex types that have nested.

Use the WebAssembly out of the browser

WebAssembly is originally known to be used on the web browser. But due to the WebAssembly being a binary format that can be compiled from various languages and with the runtime that is available in nearly all OS and platforms, it could become a significant material for software development.

To run a .wasm file out of the browser, you need a WebAssembly runtime — Wasmtime, Wasmer, WasmEdge, Wasm3. All have the basic functionality to run .wasm, import-export functions, etc. but functionalities in depth could be different. Most of the existing libraries for .NET are used or made by Wasmtime.

wasmtime-dotnet — A .NET library to run .wasm within the .NET application using Wasmtime. Looking into its readme may not show all options to load and run the WebAssembly. It shows only how to run WebAssembly from the text as below code snippet (see the definition and toolkit for wat in WebAssembly Toolkit section below).

using var module = Module.FromText(
    engine,
    "hello",
    "(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))"
);

It actually also supports running from .wasm file using Module.FromFile() and several more options. See sample and step-by-step how to use wasmtime-dotnet in this post — WebAssembly Beyond The Browser: Running Wasm In .NET Core Applications With WASI & Wasmtime — Thinktecture AG.

Access host resources like I/O, network and more

Similar to JavaScript, WebAssembly itself cannot access the outside world aka Sandboxed. The arrival of WebAssembly/WASI: WebAssembly System Interface (github.com) enhances WebAssembly to another level. It is an API standard for the runtime to be implemented so running a .wasm that uses WASI to access host resources with different runtime should get the same behavior.

The WebAssembly System Interface is not a monolithic standard system interface, but is instead a modular collection of standardized APIs. None of the APIs are required to be implemented to have a compliant runtime. Instead, host environments can choose which APIs make sense for their use cases.

See the entire list of the proposal in WASI/Proposals.md at main · WebAssembly/WASI (github.com).

There is an SDK to turn .NET project compiling to WASI-compliant .wasm. It could turn the entire ASP.NET web into a WebAssembly module. dotnet/dotnet-wasi-sdk: An SDK for building .NET projects as standalone WASI-compliant modules (github.com)

With the magic of the below extension function.

var builder = WebApplication.CreateBuilder(args).UseWasiConnectionListener();
// or
var builder = WebApplication.CreateBuilder(args).UseWasiCustomHostServer();

Caution: Don’t actually do this! It’s purely to demonstrate that ASP.NET Core on WebAssembly can run anywhere. It’s not a sensible way to deliver a normal web app.

Use the WebAssembly as a Cloud Serverless Function

The core concept of using WebAssembly is by import and export functions so we can consider it a stateless function. The same as the serverless function on the cloud. So, there are quite many experiments trying to use WebAssembly as a cloud serverless function.

  1. Manually proxy API controller to WebAssembly function using wasmtime-dotnet as this sample does.
  2. Use dotnet/dotnet-wasi-sdk package to turn the entire asp.net project output to a WebAssembly as exposed in the above section. Docker recently announced the support of Wasm with the new dedicated containerd shim runtime from WasmEdge — Introducing the Docker+Wasm Technical Preview | Docker. They probably be the perfect fit.
  3. Some cloud providers already support WebAssembly as the Serverless function — Fermyon Spin seems to be the most mature on the market at present.

Use the WebAssembly as a package and plugin

WASM package manager

The WAPM is the package manager of WebAssembly similar to NPM. It is a product of the Wasmer. You can find both library and standalone package type. Using wapmcommand to install and run a package.

wapm install cowsay
wapm run cowsay hello wapm

WASM plugin

Some products already support using wasm as its extension or plugin. With the benefit of no boundary of language and platform to run a wasm file, it is very convenient for the developer to implement or use the custom extension or plugin in wasm.

In the past if you need to create a custom extension of Envoy, you have to write in Lua. Envoy now supports extension in wasm meaning that we can write the extension in any language that can compile to wasm. Below are a few to checkout.

If you are developing a product that aim to support importable custom module or plugin then supporting the wasm might be a good idea.

Write once run many

With the portability of the WebAssembly, we can cross the boundary of language and platform using the same codebase to use with any platform.

  • The same functions written in C# can be used in Go, Rust, or PHP and vice versa.
  • The same functions written in C# can be used in both the backend and the frontend (browser via JavaScript).

Imagine we are implementing Single Page Application website and need the business logic/validation apply at the client side before sending the payload to proceed on the server side/API. If we choose NodeJS as the backend that would not be too difficult but share the same codebase for both the client and the server side though that is not the case for .NET in the past. The WebAssembly and System.Runtime.InteropServices.JavaScript in .NET 7 made it possible.

Simply create three separate projects.

  1. A business logic project in .NET 7 or .NET Standard if aims to be using it with the older .NET version like .NET Core 3, .NET 5, or .NET 6.
  2. A backend/API project in any recent .NET framework. No need to be .NET 7 if the business logic project is a .NET Standard.
  3. A .NET 7 project with System.Runtime.InteropServices.JavaScript to proxy the business logic functions and compile to WebAssembly as well as producing the dotnet.js file. The dotnet.js file is used to create and start the .NET WebAssembly runtime.

WebAssemblyDiagram

See a very simple sample project I created panot-hong/wasm-csharp-browser (github.com).


WebAssembly Toolkit

As bonus track, the WebAssembly has various toolkit for developer to use, see the list in WebAssembly/wabt: The WebAssembly Binary Toolkit (github.com). One important toolkit is ability to convert .wasm file to .wat (WebAssembly text format) and vice versa.

Wrap Up

You may have a question since .NET Core it can run cross-platform — Windows, Linux, ARM, etc. what is the benefit to compile .NET into a wasm file, so it becomes a platform independent as long as there is WebAssembly runtime support that platform. From what I understand it is a pretty similar concept except for the wasm together with WASI, it is sandboxed and access to the resources is limited.

The mainstream of the WebAssembly runtime, toolkit, and SDK are all written in Rust. Thus, Rust has the most feature-rich and always the most recent. It is though nice to see that Microsoft is one of the Bytecode Alliance members and has the intention to support WebAssembly since the arrival of Blazor.

Many stuffs in this post are in the experimental stage and WASI is still nearly half of the entire proposal list. I would recommend using it with caution and bear in mind that things could be (breaking) changed pretty quickly. Proxy and decouple the wasm pieces as much as possible.

Related Posts

Let's check our site's accessibility easily using 'Accessibility Insights for Web' on Microsoft Edge.

Let's check our site's accessibility easily using 'Accessibility Insights for Web' on Microsoft Edge.

Hello to all the readers who have come across this article. Lately, I've been quite busy and it's taken me a while to find some free time to write on Medium. Today, I want to share some knowledge tha

read more
Awaitable Long Running Producer-Consumer

Awaitable Long Running Producer-Consumer

Normally we use producer-consumer problem to solve certain problems like write buffer, cache buffer or any operation (producer) that needs to entry data into a queue and having another operation (con

read more
Let's create our own Crypto coin, easy and in just a few minutes (no coding knowledge).

Let's create our own Crypto coin, easy and in just a few minutes (no coding knowledge).

Hello everyone who stumbled upon and came to read. I've been away from writing a blog for a while. Caught up with work, trying this and that, blah blah blah. But now it's time to come back and write

read more
Customize the website to display using Tampermonkey

Customize the website to display using Tampermonkey

Many people may feel dissatisfied with certain websites when they browse them, for example:* Disliking intrusive banner advertisements that strain the eyes. * Wishing a website had specific feature

read more
Pass Through Data Over IServiceProvider.CreateScope()

Pass Through Data Over IServiceProvider.CreateScope()

[ASP.NET] In some cases you may encounter the situation that you need to pass through some particular data over a new scope of Service Provider.For instance, when you implement a solution that inte

read more
Write Unit Tests for React Hooks using react-hooks-testing-library

Write Unit Tests for React Hooks using react-hooks-testing-library

Hooks in React are a feature that has drastically changed the way we write React. It's like undergoing plastic surgery in Korea, where some developers love the new look, while others prefer the old o

read more
Scan code with Credential Scanner on Azure DevOps

Scan code with Credential Scanner on Azure DevOps

🥳 Happy New Year 2023! 🥳Wishing everyone a great year ahead!Now, let's get down to business. Today, I'm going to introduce you to a handy tool that checks for leaked passwords, secrets, certifi

read more
Convert interface to enum (for those too lazy to type 'name' in the input form) in TypeScript

Convert interface to enum (for those too lazy to type 'name' in the input form) in TypeScript

![Convert interface to enum cover](/images/posts/transform-interface-as-enum-typescript/transform_interface-as-enum-cover.png)It's a small trick to convert an Interface to an Enum, helping to solve

read more