NetWasm 0.6.0 guide

Run C# in a Web Worker.

Publish C# behind an asynchronous worker boundary, keep the page responsive while it runs, and call it through a generated JavaScript client. Each worker owns its Wasm instance and managed heap.

Choose the contract your page needs

NetWasm 0.6.0 ships two worker templates. Both generate the worker bootstrap, deployment manifest, and Promise-based page client.

  • WIT worker: choose --worker wit for a language-neutral interface described by WIT and packaged as a WebAssembly Component.
  • JSExport worker: choose --worker jsexport when JavaScript should call bounded managed methods marked with [JSExport].

A worker does not make a calculation inherently faster. It moves that work off the page's event loop so the interface can remain responsive.

Create and publish the worker

Install the 0.6.0 templates, create either worker shape, and publish the included working page:

terminal
$ dotnet new install NetWasm.Templates@0.6.0
$ dotnet new netwasm-app -n ReportWorker --worker jsexport
# Or use: --worker wit
$ cd ReportWorker
$ dotnet publish -c Release

The static deployment is written to bin/Release/netwasm0.1/publish. It contains index.html, the compiled worker, its host files, and browser/ReportWorker.worker-client.mjs.

Call C# from the page

Import createWorker from the generated client. Creation waits until the worker is ready, and every exported call returns a Promise.

page.mjs
import { createWorker } from "./browser/ReportWorker.worker-client.mjs";

const worker = await createWorker({
  onNotification({ operation, arguments: values }) {
    console.log(operation, ...values);
  },
});

try {
  const result = await worker.run(5);
  document.querySelector("#result").textContent = String(result);
} finally {
  await worker.dispose();
}

The JSExport template exposes the declared export name directly, so the example above calls worker.run(5). The WIT template exposes its qualified contract name, such as worker["netwasm:worker-template/work@1.0.0/run"].

Handle failures and lifecycle

If an exported Task or ValueTask faults, the Promise rejects with the managed message. error.managed carries the available type, message, and managed stack trace. Debug builds include source files and line numbers when portable PDB information is available.

  • dispose() lets already accepted calls finish before the worker closes.
  • terminate() stops immediately and rejects pending calls.
  • Calls made after either operation are rejected.

Validate page input before calling the worker and catch rejected Promises where the page can show a useful message. One failed managed call does not make the worker unusable.

Serve the published files

Serve the complete publish directory from an ordinary static HTTP host. Open index.html through HTTP rather than directly from the filesystem so JavaScript modules and Wasm load with normal browser rules.

The worker cannot access the page DOM. Return values to the caller or use generated notifications to ask the page to update its interface.

Current limits

Each worker has a separate managed heap. Managed references do not cross between the page and worker, and workers do not share managed objects with each other. The boundary carries values described by WIT or the supported JSExport contract.

Worker projects are browser deployments and cannot be launched with dotnet run. Publish them and use the generated page client. The complete SDK guide documents application JavaScript imports, notifications, WIT properties, and the exact boundary.

Ready to build?

Put C# behind a worker boundary.