Skip to content

LostBeard/SpawnDev.BlazorJS

Repository files navigation

SpawnDev.BlazorJS

NuGet

Full Blazor WebAssembly and JavaScript interop. Over 1,000 strongly typed C# wrappers for browser APIs - create JavaScript objects, access properties, call methods, and handle events the .NET way without writing JavaScript.

Full API Documentation - Complete MDN-style API reference with guides, 1,000+ typed wrapper references, and real C# examples.

Live Demo

Supported .NET Versions

  • .NET 8, 9, and 10
  • Blazor WebAssembly Standalone App
  • Blazor Web App - Interactive WebAssembly mode without prerendering

Note: Version 3.x dropped support for .NET 6 and 7. Use version 2.x for those targets.

Important: PublishTrimmed and RunAOTCompilation must be set to false in your project file. Trimming removes types needed for JS interop.

SpawnDev.BlazorJS.WebWorkers is now in a separate repo.

Installation

dotnet add package SpawnDev.BlazorJS

Quick Start

Two changes to Program.cs:

using SpawnDev.BlazorJS;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// Add BlazorJSRuntime
builder.Services.AddBlazorJSRuntime();

// Use BlazorJSRunAsync instead of RunAsync
await builder.Build().BlazorJSRunAsync();

Inject into any component or service:

[Inject]
BlazorJSRuntime JS { get; set; }

Basic usage:

// Get and Set global properties
var innerHeight = JS.Get<int>("window.innerHeight");
JS.Set("document.title", "Hello World!");

// Call global methods
var item = JS.Call<string?>("localStorage.getItem", "key");
JS.CallVoid("console.log", "Hello from Blazor!");

// Async methods (Promise-returning)
var response = await JS.CallAsync<Response>("fetch", "/api/data");

// Create new JS objects
using var audio = new Audio("song.mp3");
await audio.Play();

// Typed browser API access
using var window = JS.Get<Window>("window");
window.OnResize += Window_OnResize;
// ... later, before disposing:
window.OnResize -= Window_OnResize;

void Window_OnResize() => Console.WriteLine("Window resized!");

// Null-conditional member access
var size = JS.Get<int?>("fruit.options?.size");

For full setup details including worker scope detection and WebWorkerService, see the Getting Started Guide.


Key Features

Feature Description Docs
1,000+ Typed Wrappers Every major browser API - DOM, WebGPU, WebRTC, WebAudio, Crypto, WebXR, and more API Reference
BlazorJSRuntime Get, Set, Call, CallAsync, New - with null-conditional (?.) support Guide
JSObject Base class for typed JS wrappers with automatic disposal Guide
ActionEvent Type-safe event subscription with += / -= and automatic ref counting Guide
Callbacks Pass .NET methods to JS - Create, CreateOne, CallbackGroup Guide
Union Types TypeScript-style discriminated unions with Match, Map, Reduce Guide
Undefinable Distinguish null from undefined in JS interop Guide
TypedArrays Full typed array support - Uint8Array, Float32Array, ArrayBuffer, etc. Guide
HeapView Zero-copy data sharing by pinning .NET arrays in WASM memory Guide
Promises JS Promise wrapper - create from Task, lambda, or TaskCompletionSource Guide
Worker Scopes Detect Window, DedicatedWorker, SharedWorker, ServiceWorker contexts Guide
Custom Wrappers Wrap any JS library in typed C# - step-by-step guide Guide
Disposal Managing JSObject, Callback, and reference lifetimes Guide
EnumString Bidirectional enum-to-JS-string mapping Guide
Blazor Web App .NET 8+ compatibility with prerendering support Guide

Sync vs Async - Important

SpawnDev.BlazorJS is a 1:1 mapping to JavaScript. Use the correct call type or it will throw:

JS Method Returns C# Call Wrong
Value (sync) JS.Call<T>(), JS.Get<T>() CallAsync on sync method throws
Promise (async) JS.CallAsync<T>() Call on Promise returns wrong type
void (sync) JS.CallVoid() -
void Promise (async) JS.CallVoidAsync() CallVoid on async method won't await
// Sync JS method - use sync call
var total = JS.Call<int>("addNumbers", 20, 22);

// Async JS method or Promise-returning - use async call
var data = await JS.CallAsync<string>("fetchData");

// Async void (Promise with no return value)
await JS.CallVoidAsync("someAsyncVoidMethod");

See the BlazorJSRuntime Guide for full details.


Browser API Examples

WebSocket

using var ws = new WebSocket("wss://echo.websocket.org");
ws.BinaryType = "arraybuffer";
ws.OnOpen += WS_OnOpen;
ws.OnMessage += WS_OnMessage;
ws.OnClose += WS_OnClose;

// ... when done, always unsubscribe before disposing:
ws.OnOpen -= WS_OnOpen;
ws.OnMessage -= WS_OnMessage;
ws.OnClose -= WS_OnClose;

void WS_OnOpen() => ws.Send("Hello!");
void WS_OnMessage(MessageEvent msg) => Console.WriteLine($"Received: {msg.Data}");
void WS_OnClose(CloseEvent e) => Console.WriteLine($"Closed: {e.Code}");

Fetch API

using var response = await JS.CallAsync<Response>("fetch", "/api/data");
if (response.Ok)
{
    var text = await response.Text();
    Console.WriteLine(text);
}

IndexedDB

using var idbFactory = new IDBFactory();
using var db = await idbFactory.OpenAsync("myDB", 1, (evt) =>
{
    using var request = evt.Target;
    using var database = request.Result;
    database.CreateObjectStore<string, MyData>("store", new IDBObjectStoreCreateOptions { KeyPath = "id" });
});
using var tx = db.Transaction("store", "readwrite");
using var store = tx.ObjectStore<string, MyData>("store");
await store.PutAsync(new MyData { Id = "1", Name = "Test" });

Web Crypto

using var crypto = new Crypto();
using var subtle = crypto.Subtle;
using var keys = await subtle.GenerateKey<CryptoKeyPair>(
    new EcKeyGenParams { Name = "ECDSA", NamedCurve = "P-384" },
    false, new[] { "sign", "verify" });
using var signature = await subtle.Sign(
    new EcdsaParams { Hash = "SHA-384" }, keys.PrivateKey!, testData);
var valid = await subtle.Verify(
    new EcdsaParams { Hash = "SHA-384" }, keys.PublicKey!, signature, testData);

ActionEvent (Event Handling)

using var window = JS.Get<Window>("window");

// Attach event handler - reference counting is automatic
window.OnStorage += HandleStorageEvent;

// Detach - IMPORTANT: always detach before disposing to prevent leaks
window.OnStorage -= HandleStorageEvent;

void HandleStorageEvent(StorageEvent e)
{
    Console.WriteLine($"Storage changed: {e.Key}");
}

Custom JSObject Wrapper

// Wrap any JS library without writing JavaScript
public class Audio : JSObject
{
    public Audio(IJSInProcessObjectReference _ref) : base(_ref) { }
    public Audio(string url) : base(JS.New("Audio", url)) { }
    
    public string Src { get => JSRef!.Get<string>("src"); set => JSRef!.Set("src", value); }
    public double Volume { get => JSRef!.Get<double>("volume"); set => JSRef!.Set("volume", value); }
    public Task Play() => JSRef!.CallVoidAsync("play");
    public Task Pause() => JSRef!.CallVoidAsync("pause");
    
    public ActionEvent OnEnded { get => new ActionEvent("ended", AddEventListener, RemoveEventListener); set { } }
}

See the Custom JSObject Guide for the full walkthrough.

For 930+ more typed wrappers, see the Complete API Reference.


Unit Testing

This project uses Playwright .NET for unit testing in a real browser with an actual JavaScript environment.

  • SpawnDev.BlazorJS.Demo - Demo project with unit test methods
  • PlaywrightTestRunner - Playwright test runner project
  • PlaywrightTestRunner/_test.bat / _test.sh - Build and run tests on Windows / Linux
  • .github/workflows/playwright-test-runner.yml - CI testing on GitHub Actions

Issues and Feature Requests

If you find a bug or missing properties, methods, or JavaScript objects please submit an issue here on GitHub. I will help as soon as possible.

Create a new discussion to show off your projects and post your ideas.

Support for Us

Sponsor us via GitHub Sponsors to give us more time to work on SpawnDev.BlazorJS and other open source projects. Or buy us a cup of coffee via PayPal. All support is greatly appreciated!

GitHub Sponsor Donate

Thank you to everyone who has helped support SpawnDev.BlazorJS and related projects financially, by filing issues, and by improving the code. Every bit helps!

Demos

BlazorJS and WebWorkers Demo: https://blazorjs.spawndev.com/