Using .NET code from JavaScript using WebAssembly

 
 
  • Gérald Barré

Blazor WebAssembly allows running 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">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    <WasmMainJSPath>main.js</WasmMainJSPath>

    <!-- JSExport requires unsafe code -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

    <!-- Reduce output size -->
    <InvariantGlobalization>true</InvariantGlobalization>
    <WasmEmitSymbolMap>false</WasmEmitSymbolMap>
  </PropertyGroup>

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

</Project>

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.

C#
using System.Runtime.InteropServices.JavaScript;

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

public partial class Sample
{
    // Make the method accessible from JS
    [JSExport]
    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
    .withDiagnosticTracing(false)
    .create();

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>
<html>
  <head>
    <title>Sample</title>
    <meta charset="UTF-8">
  </head>
  <body>
    <div id="out"></div>
    <script type="module" src="main.js"></script>
  </body>
</html>

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

Shell
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.

Download the code from this post: sample-dotnet-webassembly.zip.

#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