Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiling C# project to WebAssembly

I need to compile a C# project to WebAssembly and be able to call some methods from JavaScript.

I want to use it in an old ASP.NET MVC 4 application that needs to add some new features and I prefer to use C# instead JavaScript/TypeScript.

Ideally I would like to compile to WebAssembly using .Net 6 but I can use any other alternative.

I'm running .Net 6 on Windows 10 Version 21H1 (OS Build 19043.1415)

I've installed:

  • Visual Studio 2022
  • the workload "wasm-tools" (.NET WebAssembly build tools)

But every time I search for a tutorial, example, etc, about how to use the .NET WebAssembly build tools the results are about Blazor.

I've read this tutorial but I can't find the mono-wasm compiler (and like I said above I would like to use .Net 6 to compile whenever possible.)

Can anyone please help me with this?

Thank you.

like image 356
vcRobe Avatar asked Sep 12 '25 13:09

vcRobe


1 Answers

There is the experimental NativeAOT-LLVM (https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM). It is not an official Microsoft WebAssembly compiler, its supported by the community, but .Net 6 is available. First, and this only works on Windows, you need to install and activate emscripten. I wont cover installing emscripten here, but you can read https://emscripten.org/docs/getting_started/downloads.html.

To create and run a dotnet c# library:

  1. Create the library project:
dotnet new classlib
  1. Create the library code, we'll use something simple that avoids any problems marshalling things like javascript strings, so in the Class1.cs file add
[System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "Answer")]
public static int Answer()
{
    return 41;
}

This will create a function, Answer that can be called from outside managed code, i.e. from Javascript

  1. Add a nuget.config
dotnet new nugetconfig
  1. In the nuget.config add the reference to allow the experimental package to be downloaded. You can also change the package download location to avoid it adding experimental packages to your global nuget location. Your nuget.config should look like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="globalPackagesFolder" value=".packages" />
  </config>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>
  1. Add the package references for the compiler to your project's csproj file so that it ends with this:
  <ItemGroup>
    <PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="7.0.0-*" />
    <PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="7.0.0-*" />
  </ItemGroup>

</Project>
  1. Publish your project to wasm:
dotnet publish /p:NativeLib=Static /p:SelfContained=true -r browser-wasm -c Debug /p:TargetArchitecture=wasm /p:PlatformTarget=AnyCPU /p:MSBuildEnableWorkloadResolver=false /p:EmccExtraArgs="-s EXPORTED_FUNCTIONS=_Answer%2C_NativeAOT_StaticInitialization -s EXPORTED_RUNTIME_METHODS=cwrap" --self-contained

This will build the project referencing the browser-wasm runtime. MSBuildEnableWorkloadResolver stops the build process checking for Mono's wasm-tools Visual Studio workload which we are not using here. (Mono is a different compiler and runtime, which I believe is getting similar support for .net 7). EmccExtraArgs allows us to add parameters to emscripten's emcc and we need that to export the two function we will call from Javascript: Answer - this is our library function, and NativeAOT_StaticInitialization this is called once per lifetime of the wasm module to initialize the runtime. Note the additional underscores in front of the names. The compilation takes a while, but when finished you should have a subfolder bin\x64\Debug\net6.0\browser-wasm\native where you will find the wasm, some html, and some javascript. In the html file at the end, before the closing body tag, initialize the runtime and call your function with:

<script>
    Module.onRuntimeInitialized = _ => {
    const corertInit = Module.cwrap('NativeAOT_StaticInitialization', 'number', []);
    corertInit();

    const answer = Module.cwrap('Answer', 'number', []);
    console.log(answer());
    };
</script>

Then server that up with the web server of your choosing, browse to the page and check the console where if everything has gone to plan, and the stars align (this is experimental), you should see enter image description here

like image 109
S Waye Avatar answered Sep 15 '25 03:09

S Waye