JSObject is the base class for all 1,021 typed JavaScript wrappers in SpawnDev.BlazorJS. It wraps an IJSInProcessObjectReference and provides strongly typed access to JavaScript object properties, methods, and events.
Namespace: SpawnDev.BlazorJS
Every JSObject holds a reference to a JavaScript object via IJSInProcessObjectReference (stored in the JSRef property). When you call methods like Get<T>() or CallVoid() on JSRef, they invoke the corresponding JavaScript operations on the underlying object. When the JSObject is disposed, the JavaScript reference is released.
C# JSObject (Window)
|
+-- JSRef: IJSInProcessObjectReference -> JS: window
|
+-- .Get<string>("name") -> window.name
+-- .CallVoid("alert", "Hi") -> window.alert("Hi")
+-- .Set("name", "foo") -> window.name = "foo"
Every class that inherits from JSObject must have a constructor that takes a single IJSInProcessObjectReference parameter. This is the deserialization constructor - it is called by the interop system when a JavaScript object is being returned to .NET.
public class MyWrapper : JSObject
{
// REQUIRED - the interop system calls this when deserializing JS objects to C#
public MyWrapper(IJSInProcessObjectReference _ref) : base(_ref) { }
}Without this constructor, the wrapper cannot be used as a return type from Get<T>(), Call<T>(), or CallAsync<T>().
Optionally, add constructors that create new JavaScript objects by calling JS.New():
public class Audio : JSObject
{
// Deserialization constructor (required)
public Audio(IJSInProcessObjectReference _ref) : base(_ref) { }
// Creation constructor - creates new Audio("url")
public Audio(string url) : base(JS.New("Audio", url)) { }
}The JS property used here is a protected static property on JSObject that refers to the singleton BlazorJSRuntime instance.
JSRef is the underlying IJSInProcessObjectReference that points to the JavaScript object:
public class Window : EventTarget
{
public Window(IJSInProcessObjectReference _ref) : base(_ref) { }
// Use JSRef to access JS properties and methods
public string? Name
{
get => JSRef!.Get<string>("name");
set => JSRef!.Set("name", value);
}
public void Alert(string message) => JSRef!.CallVoid("alert", message);
}JSRef is null after disposal. The ! null-forgiving operator is used because in normal usage JSRef is non-null.
Map JavaScript properties to C# properties using Get<T> and Set:
public class HTMLVideoElement : HTMLMediaElement
{
public HTMLVideoElement(IJSInProcessObjectReference _ref) : base(_ref) { }
// Read-write property
public int VideoWidth => JSRef!.Get<int>("videoWidth");
public int VideoHeight => JSRef!.Get<int>("videoHeight");
// Read-write string property
public string Src
{
get => JSRef!.Get<string>("src");
set => JSRef!.Set("src", value);
}
// Nullable property
public double? Duration => JSRef!.Get<double?>("duration");
// Property returning another JSObject (caller must dispose)
public VideoPlaybackQuality GetVideoPlaybackQuality()
=> JSRef!.Call<VideoPlaybackQuality>("getVideoPlaybackQuality");
}Map JavaScript methods using Call<T>, CallVoid, and CallAsync<T>:
public class Storage : JSObject
{
public Storage(IJSInProcessObjectReference _ref) : base(_ref) { }
// Synchronous methods
public string? GetItem(string key) => JSRef!.Call<string?>("getItem", key);
public void SetItem(string key, string value) => JSRef!.CallVoid("setItem", key, value);
public void RemoveItem(string key) => JSRef!.CallVoid("removeItem", key);
public void Clear() => JSRef!.CallVoid("clear");
public int Length => JSRef!.Get<int>("length");
}
public class MediaDevices : EventTarget
{
public MediaDevices(IJSInProcessObjectReference _ref) : base(_ref) { }
// Async methods (Promise-returning in JS)
public Task<MediaStream> GetUserMedia(object constraints)
=> JSRef!.CallAsync<MediaStream>("getUserMedia", constraints);
public Task<MediaDeviceInfo[]> EnumerateDevices()
=> JSRef!.CallAsync<MediaDeviceInfo[]>("enumerateDevices");
}Use the ActionEvent pattern to expose JavaScript events with += and -= operators:
public class Window : EventTarget
{
public Window(IJSInProcessObjectReference _ref) : base(_ref) { }
// ActionEvent pattern - the set { } is required for the += operator to work
public ActionEvent<StorageEvent> OnStorage
{
get => new ActionEvent<StorageEvent>("storage", AddEventListener, RemoveEventListener);
set { }
}
public ActionEvent OnOnline
{
get => new ActionEvent("online", AddEventListener, RemoveEventListener);
set { }
}
public ActionEvent OnOffline
{
get => new ActionEvent("offline", AddEventListener, RemoveEventListener);
set { }
}
}See Events (ActionEvent / FuncEvent) for full details.
JSObject implements IDisposable and has a finalizer. The finalizer will clean up the underlying IJSInProcessObjectReference, but you should always dispose explicitly:
// Using statement (preferred)
using var window = JS.Get<Window>("window");
window.Alert("Hello!");
// window is disposed at end of scope
// Explicit disposal
var doc = JS.Get<Document>("document");
var title = doc.Title;
doc.Dispose();The IsWrapperDisposed property indicates whether the wrapper has been disposed.
Returns a copy of the reference as type T. The original JSObject remains valid. Both methods are synonyms.
using var eventTarget = JS.Get<EventTarget>("myElement");
using var element = eventTarget.JSRefAs<HTMLElement>();
// Both eventTarget and element point to the same JS object
// Both must be disposedMoves the reference to type T and disposes the original wrapper. Use this when you want to convert a type without keeping the original.
using var element = eventTarget.JSRefMove<HTMLElement>();
// eventTarget is now disposed - do not use it
// element owns the referenceReturns the raw IJSInProcessObjectReference and disposes the wrapper:
IJSInProcessObjectReference? rawRef = myJSObject.JSRefMove();
// myJSObject is disposed, you own rawRef and must dispose it yourselfReturns a copy of the IJSInProcessObjectReference:
IJSInProcessObjectReference copy = myJSObject.JSRefCopy();
// myJSObject still valid, copy must be disposed separatelyTest if a JSObject is of a specific JavaScript type:
// Test by type name
if (myObj.JSRefIs<HTMLCanvasElement>())
{
// The JS object's constructor.name == "HTMLCanvasElement"
}
// Test and convert in one step
if (myObj.JSRefIs<HTMLCanvasElement>(out HTMLCanvasElement canvas))
{
canvas.Width = 800;
canvas.Dispose();
}
// Test with custom constructor name
if (myObj.JSRefIs<JSObject>("MyCustomClass", out JSObject custom))
{
// ...
}Compare using JavaScript equality:
bool equal = obj1.JSEquals(obj2); // JS ==
bool strictEqual = obj1.JSEquals(obj2, true); // JS ===Creates an instance of type T that will be serialized to JavaScript as undefined:
var undefinedWindow = JSObject.Undefined<Window>();
JS.Set("_undefinedWindow", undefinedWindow);
var isUndef = JS.IsUndefined("_undefinedWindow");
// isUndef == trueThe generic JSObject<T> class provides a static Undefined property:
// Equivalent to JSObject.Undefined<Window>()
var undef = JSObject<Window>.Undefined;All JSObject subclasses inherit a protected static JS property:
protected static BlazorJSRuntime JS => BlazorJSRuntime.JS;This lets constructors and static methods access the runtime without injection:
public class IDBFactory : JSObject
{
public IDBFactory(IJSInProcessObjectReference _ref) : base(_ref) { }
// Use JS.Get to create from a global property
public IDBFactory() : base(JS.Get<IJSInProcessObjectReference>("indexedDB")) { }
}JSObject wrappers follow the same inheritance hierarchy as their JavaScript counterparts:
JSObject
-> EventTarget
-> Node
-> Element
-> HTMLElement
-> HTMLMediaElement
-> HTMLVideoElement
-> HTMLAudioElement
-> HTMLCanvasElement
-> HTMLInputElement
-> Window
-> Worker
-> WebSocket
-> RTCPeerConnection
-> AudioNode
-> GainNode
-> AnalyserNode
-> OscillatorNode
This means an HTMLVideoElement has access to all methods and events from HTMLMediaElement, HTMLElement, Element, Node, EventTarget, and JSObject.
Here is a complete example wrapping a simple JS library (Audio):
public class Audio : HTMLMediaElement
{
// Step 1: Deserialization constructor (required)
public Audio(IJSInProcessObjectReference _ref) : base(_ref) { }
// Step 2: Creation constructor
public Audio(string url) : base(JS.New("Audio", url)) { }
// Step 3: Map methods
public void Play() => JSRef!.CallVoid("play");
public void Pause() => JSRef!.CallVoid("pause");
// Step 4: Map properties
public double CurrentTime
{
get => JSRef!.Get<double>("currentTime");
set => JSRef!.Set("currentTime", value);
}
public double Volume
{
get => JSRef!.Get<double>("volume");
set => JSRef!.Set("volume", value);
}
}
// Usage:
var audio = new Audio("https://example.com/song.mp3");
audio.Volume = 0.5;
audio.Play();For a more complex example with events and library loading, see Custom JSObject Wrappers.