Using .NET code from JavaScript using WebAssembly

  • Gérald Barré

Blazor WebAssembly allows to run a .NET web application in a browser. Starting with .NET 7, you can easily run any .NET method from JavaScript without needing the whole Blazor framework. Let's see how to run a .NET method from JavaScript!

First, you need to install the WASM workload, so you can publish the app later:

dotnet workload install wasm-tools

Then, you can create a new console app:

dotnet new console

Let's edit the csproj file to add support for WASM:

Sample.csproj (csproj (MSBuild project file))
<Project Sdk="Microsoft.NET.Sdk">



    <!-- JSExport requires unsafe code -->

    <!-- Reduce output size -->

  <!-- Copy extra files when publishing the app -->
    <WasmExtraFilesToDeploy Include="index.html" />


Then, you can edit the Program.cs file with a static method decorated with the [JSExport] attribute. This attribute indicates the method is accessible from JS.

using System.Runtime.InteropServices.JavaScript;

// Create a "Main" method. This is required by the tooling.

public partial class Sample
    // Make the method accessible from JS
    internal static int Add(int a, int b)
        return a + b;

The [JSExport] attribute instructs the Source Generator to generate the interop code for this method. If you are curious, you can check the generated code in Visual Studio:

Then, you can create a new file named main.js at the root of the project. The file name must match the property <WasmMainJSPath> from the csproj.

main.js (JavaScript)
// Set up the .NET WebAssembly runtime
import { dotnet } from './dotnet.js'

// Get exported methods from the .NET assembly
const { getAssemblyExports, getConfig } = await dotnet

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

// Access JSExport methods using exports.<Namespace>.<Type>.<Method>
const result = exports.Sample.Add(1, 2);

// Display the result of the .NET method
document.getElementById("out").innerHTML = `Result: ${result}`;

Finally, you need to create an html file to load the JS module.

index.html (HTML)
<!DOCTYPE html>
    <meta charset="UTF-8">
    <div id="out"></div>
    <script type="module" src="main.js"></script>

The source code is ready. The final step is to publish the application:

dotnet publish --configuration Release

The result is available in the folder bin/Release/net7.0/browser-wasm/AppBundle. You can open the index.html file from this folder to see the result.

#Application size

The uncompressed publish folder is 2.81MB. But, you need to compress the output using brotli to get the actual result. Also, by default, the file dotnet.timezones.blat is included. I could not find a way to remove it. So, I've edited the mono-config.json file directly to remove it. I also removed System.Private.Uri.dll (26kB) which is not used in this sample. I'm not sure why the linker cannot remove this file automatically.

The final size is 819kB. Here's the breakdown:

Payload size (brotli): ~819KB

  • dotnet.js ⇒ 54kB
  • dotnet.wasm ⇒ 342kB
  • SampleProject.dll: 3kB
  • System.Private.CoreLib.dll: 407kB
  • System.Runtime.dll: 2kB
  • System.Runtime.InteropServices.JavaScript.dll: 14kB

The baseline, (dotnet.js, dotnet.wasm, and System.Private.CoreLib.dll) is quite big. Other dlls, are small and are pay for use thanks to the linker.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub