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.
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.
The WebAssembly is function-based, and you can use it either.
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
GetPropertyAs[Type](“propertyname”)functions to retrieve property value.
- 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
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.
- Manually proxy API controller to WebAssembly function using wasmtime-dotnet as this sample does.
- 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.
- 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
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.
- Istio / Wasm Plugin
- wasmerio/wasmer-postgres: 💽🕸 Postgres library to run WebAssembly binaries. (github.com)
- WASM — envoy 1.25.0-dev-23eacb documentation (envoyproxy.io)
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.
Simply create three separate projects.
- 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.
- A backend/API project in any recent .NET framework. No need to be .NET 7 if the business logic project is a .NET Standard.
dotnet.jsfile is used to create and start the .NET WebAssembly runtime.
See a very simple sample project I created panot-hong/wasm-csharp-browser (github.com).
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.
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.