BlazorJSRuntime is the central JavaScript interop service in SpawnDev.BlazorJS. It provides synchronous and asynchronous access to the JavaScript global scope, including property access, method calls, object creation, type checking, script loading, and scope detection.
Namespace: SpawnDev.BlazorJS
Register in Program.cs:
builder.Services.AddBlazorJSRuntime();Or with early access (the out var JS pattern):
builder.Services.AddBlazorJSRuntime(out var JS);
// JS is available immediately for scope detection, script loading, etc.[Inject]
BlazorJSRuntime JS { get; set; }public class MyService
{
private readonly BlazorJSRuntime _js;
public MyService(BlazorJSRuntime js) => _js = js;
}All JSObject subclasses have access to a protected static JS property that refers to the singleton BlazorJSRuntime:
public class MyWrapper : JSObject
{
// This static JS property is provided by JSObject base class
// It is equivalent to BlazorJSRuntime.JS
public MyWrapper(string url) : base(JS.New("MyClass", url)) { }
public MyWrapper(IJSInProcessObjectReference _ref) : base(_ref) { }
}The static BlazorJSRuntime.JS property is also accessible from anywhere:
var height = BlazorJSRuntime.JS.Get<int>("window.innerHeight");Reads a JavaScript property and deserializes it to type T. Supports dot notation and null-conditional ?. operator.
// Primitive types
int height = JS.Get<int>("window.innerHeight");
string title = JS.Get<string>("document.title");
bool hidden = JS.Get<bool>("document.hidden");
double ratio = JS.Get<double>("window.devicePixelRatio");
// JSObject types - REMEMBER to dispose
using var window = JS.Get<Window>("window");
using var document = JS.Get<Document>("document");
using var navigator = JS.Get<Navigator>("navigator");
using var localStorage = JS.Get<Storage>("localStorage");
// Nullable with null-conditional
int? size = JS.Get<int?>("fruit.options?.size");
string? theme = JS.Get<string?>("app?.config?.theme");
// IJSInProcessObjectReference (raw reference)
var rawRef = JS.Get<IJSInProcessObjectReference>("someObject");Sets a JavaScript property.
JS.Set("document.title", "My App");
JS.Set("myGlobalVar", 42);
JS.Set("myGlobalObj", new { name = "test", count = 5 });
// Set to null
JS.Set("myVar", null);Deletes a JavaScript property (equivalent to the delete operator).
JS.Delete("myGlobalVar");Calls a synchronous JavaScript method and returns the result as type T.
// Returns a value
string? item = JS.Call<string?>("localStorage.getItem", "myKey");
int max = JS.Call<int>("Math.max", 10, 20);
double random = JS.Call<double>("Math.random");
string json = JS.Call<string>("JSON.stringify", myObject);
// Returns a JSObject type
using var parsed = JS.Call<JSObject>("JSON.parse", jsonString);Calls a synchronous JavaScript method that returns void.
JS.CallVoid("console.log", "Hello from Blazor!");
JS.CallVoid("localStorage.setItem", "key", "value");
JS.CallVoid("localStorage.removeItem", "key");
JS.CallVoid("alert", "Warning!");Calls a JavaScript method that returns a Promise, awaits it, and returns the resolved value as type T.
// Fetch API
using var response = await JS.CallAsync<Response>("fetch", "/api/data");
// Any async/Promise-returning function
string result = await JS.CallAsync<string>("myAsyncFunction", arg1);SpawnDev.BlazorJS is a 1:1 mapping to JavaScript. Using the wrong call type throws an error.
| JavaScript Returns | Correct C# Call | Wrong Call |
|---|---|---|
| Value (synchronous) | JS.Call<T>() or JS.Get<T>() |
JS.CallAsync<T>() THROWS |
| Promise (asynchronous) | JS.CallAsync<T>() |
JS.Call<T>() returns a Promise object, not the value |
| void (synchronous) | JS.CallVoid() |
- |
| void Promise (asynchronous) | JS.CallVoidAsync() |
JS.CallVoid() won't await the Promise |
Examples of correct usage:
// Synchronous JS function
// function addNum(a, b) { return a + b; }
var total = JS.Call<int>("addNum", 20, 22);
// Asynchronous JS function
// async function fetchData(url) { ... }
var data = await JS.CallAsync<string>("fetchData", "/api/items");
// Alternative for async: get the Promise, then await it
using var promise = JS.Call<Promise<string>>("fetchData", "/api/items");
var data = await promise.ThenAsync();
// Alternative for async: use Task<T>
var data = await JS.Call<Task<string>>("fetchData", "/api/items");Creates a new JavaScript object using the given constructor and returns an IJSInProcessObjectReference.
var workerRef = JS.New("Worker", "my-worker.js");Creates a new JavaScript object and wraps it in a typed JSObject wrapper.
using var audio = JS.New<Audio>("Audio", "song.mp3");
using var ws = JS.New<WebSocket>("WebSocket", "wss://example.com");
using var re = JS.New<RegExp>("RegExp", "\\d+", "g");Returns the JavaScript typeof result as a string.
string type = JS.TypeOf("someVar");
// Returns: "object", "function", "number", "string", "boolean", "undefined", "symbol", "bigint"Returns true if the JavaScript expression evaluates to undefined.
bool isUndef = JS.IsUndefined("someVar");
bool hasFeature = !JS.IsUndefined("navigator.gpu");Returns the constructor.name of a JavaScript object.
string name = JS.ConstructorName("someVar");
// Returns: "Array", "Date", "Object", "HTMLDivElement", etc.Returns constructor names for multiple expressions in a single call.
string[] names = JS.ConstructorNames("var1", "var2", "var3");Loads a JavaScript file by adding a <script> tag to the document and waiting for it to load.
await JS.LoadScript("https://cdn.example.com/library.js");
await JS.LoadScript("js/my-utils.js");
await JS.LoadScript("_content/MyPackage/interop.js");Loads an ES module using dynamic import().
using var module = await JS.Import("./my-module.js");Convenience wrapper for the Fetch API.
using var response = await JS.Fetch("/api/data");
var text = await response.Text();These properties detect the current JavaScript global scope. Critical for apps that run in both Window and Worker contexts.
| Property | Type | Description |
|---|---|---|
IsWindow |
bool |
true if running in a browser Window |
IsWorker |
bool |
true if running in any Worker scope |
IsDedicatedWorkerGlobalScope |
bool |
true if running in a dedicated Worker |
IsSharedWorkerGlobalScope |
bool |
true if running in a shared Worker |
IsServiceWorkerGlobalScope |
bool |
true if running in a service Worker |
GlobalScope |
GlobalScope (enum) |
The current scope as an enum value |
GlobalThisTypeName |
string |
The constructor.name of globalThis |
CrossOriginIsolated |
bool? |
Whether the site is cross-origin isolated (needed for SharedArrayBuffer) |
IsBrowser |
bool |
true if running in a browser (shortcut for OperatingSystem.IsBrowser()) |
Tests if the current scope matches the given scope flags:
if (JS.IsScope(GlobalScope.Window | GlobalScope.DedicatedWorker))
{
// Running in either Window or DedicatedWorker
}| Property | Type | Description |
|---|---|---|
GlobalThis |
JSObject? |
The globalThis object |
WindowThis |
Window? |
Non-null when IsWindow is true |
DedicatedWorkerThis |
DedicatedWorkerGlobalScope? |
Non-null when IsDedicatedWorkerGlobalScope is true |
SharedWorkerThis |
SharedWorkerGlobalScope? |
Non-null when IsSharedWorkerGlobalScope is true |
ServiceWorkerThis |
ServiceWorkerGlobalScope? |
Non-null when IsServiceWorkerGlobalScope is true |
Convenience wrapper for console.log:
JS.Log("Hello", myObject, 42);A unique hex string generated when the app starts, useful for identifying instances:
string id = JS.InstanceId; // e.g. "A1B2-C3D4-E5F6-7890"Elapsed time (in milliseconds) until all background services have started.
Returns true if the IJSInProcessRuntime is available (always true in Blazor WASM).
The ?. operator in property paths prevents errors when intermediate properties are null or undefined:
// Safely access nested properties
var size = JS.Get<int?>("fruit.options?.size");
// If fruit.options is null/undefined, returns null instead of throwing
// Multiple null-conditional operators
var value = JS.Get<string?>("app?.config?.settings?.theme");This works with Get<T>, Set, Call<T>, CallVoid, and CallAsync<T>.