SpawnDev.BlazorJS
Full Blazor WebAssembly and Javascript interop. Supports all Javascript data types and web browser APIs.
Install / Use
/learn @LostBeard/SpawnDev.BlazorJSREADME
SpawnDev.BlazorJS
Full Blazor WebAssembly and Javascript interop. Create Javascript objects, access properties, call methods, and add/remove event handlers of any Javascript objects the .Net way without writing Javascript.
SpawnDev.BlazorJS.WebWorkers is now in a separate repo here.
Supported .Net Versions
- .Net 8, 9 and 10
- Blazor WebAssembly Standalone App
- Blazor Web App - Interactive WebAssembly mode without prerendering
Note: SpawnDev.BlazorJS version 3.x has dropped support for .Net 6 and 7. If you need to target .Net 6 or 7, please use version 2.x.
Features:
- Supports all web browser Web APIs
- If we missed anything, open an issue and it will be updated ASAP.
- Supports all web browser Javascript data types
- Over 450 strongly typed JSObject wrappers (listed here) included in BlazorJS including DOM, Crypto, WebGL, WebRTC, Atomics, TypedArrays, and Promises allow direct interaction with Javascript
- Use Javascript libraries in Blazor without writing any Javascript code
- BlazorJSRuntime wraps the default JSRuntime adding additional functionality
- Create new Javascript objects directly from Blazor
- Get and set Javascript object properties as well as access methods
- Easily pass .Net methods to Javascript using ActionEvent, Callback.Create or Callback.CreateOne methods
- Easily wrap your Javascript objects for direct manipulation from Blazor (No javascript required!)
- Create a class that inherits from JSObject and define the methods, properties, events, and constructors.
- Supports Promise, Union method parameters, and passing undefined to Javascript
- Supports Tuple, ValueTuple serialization to and from a Javascript Array
- Supports null-conditional member access operator ?. in JS interop
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.
BlazorJSRuntime
Getting started. Using BlazorJS requires 2 changes to your Program.cs.
- Add the BlazorJSRuntime service with builder.Services.AddBlazorJSRuntime()
- Initialize BlazorJSRuntime by calling builder.Build().BlazorJSRunAsync() instead of builder.Build().RunAsync()
- Supports null-conditional member access operator ?. in JS interop
// ... other usings
using SpawnDev.BlazorJS;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Add SpawnDev.BlazorJS.BlazorJSRuntime
builder.Services.AddBlazorJSRuntime();
// build and Init using BlazorJSRunAsync (instead of RunAsync)
await builder.Build().BlazorJSRunAsync();
Inject into components
[Inject]
BlazorJSRuntime JS { get; set; }
Examples uses
// Get and Set
var innerHeight = JS.Get<int>("window.innerHeight");
JS.Set("document.title", "Hello World!");
// Call
var item = JS.Call<string?>("localStorage.getItem", "itemName");
JS.CallVoid("addEventListener", "resize", Callback.Create(() => Console.WriteLine("WindowResized"), _callBacks));
// Attach events
using var window = JS.Get<Window>("window");
window.OnOffline += Window_OnOffline;
// AddEventListener and RemoveEventListener are supported on all EventTarget objects
window.AddEventListener("resize", Window_OnResize, true);
window.RemoveEventListener("resize", Window_OnResize, true);
IMPORTANT NOTE - Async vs Sync Javascript calls
SpawnDev's BlazorJSRuntime behaves differently than Microsoft's Blazor JSRuntime. SpawnDev's BlazorJSRuntime is more of a 1 to 1 mapping to Javascript.
When calling Javascript methods that are not asynchronous and do not return a Promise you need to use the synchronous BlazorJSRuntime methods Call, CallVoid, or Get. Unlike the default Blazor JSRuntime which would allow the use of InvokeAsync, you must use the synchronous BlazorJSRuntime methods.
Use synchronous BlazorJSRuntime calls for synchronous Javascript methods. BlazorJSRuntime CallAsync would throw an error if used on the below Javascript method.
// Javascript
function AddNum(num1, num2){
return num1 + num2;
}
// C#
var total = JS.Call<int>("AddNum", 20, 22);
// total == 42 here
Use synchronous BlazorJSRuntime calls for asynchronous Javascript methods.
// Javascript
async function AddNum(num1, num2){
return num1 + num2;
}
// C#
var total = await JS.Call<Task<int>>("AddNum", 20, 22);
// total == 42 here
// C#
var totalPromise = JS.Call<Promise<int>>("AddNum", 20, 22);
var total = await totalPromise.ThenAsync();
// total == 42 here
Use asynchronous BlazorJSRuntime calls for asynchronous Javascript methods.
// Javascript
async function AddNum(num1, num2){
return num1 + num2;
}
// C#
var total = await JS.CallAsync<int>("AddNum", 20, 22);
// total == 42 here
Use asynchronous BlazorJSRuntime calls for methods that return a Promise.
// Javascript
function AddNum(num1, num2){
return new Promise((resolve, reject)=>{
resolve(num1 + num2);
});
}
// C#
var total = await JS.CallAsync<int>("AddNum", 20, 22);
// total == 42 here
NULL Conditional
The BlazorJSRuntime now supports null-conditional member access operator ?..
Note: The null-conditional member access operator ?. is also known as the Elvis operator.
Example
// Javascript
var fruit = {
name: 'apple',
color: 'red'
};
The below JS.Get would throw an error because fruit.options does not exist, and therefore we cannot access a property of it.
// C#
var size = JS.Get<int?>("fruit.options.size");
// never gets here due to error because `fruit.options` does not exist
Using a null conditional (the ? in fruit.options?.size) prevents the error by allowing fruit.options to not exist (null or undefined.)
// C#
var size = JS.Get<int?>("fruit.options?.size");
// size == null here (default value for int?)
IJSInProcessObjectReference extended
// Get Set
var window = JS.Get<IJSInProcessObjectReference>("window");
window.Set("myVar", 5);
var myVar = window.Get<int>("myVar");
// Call
window.CallVoid("addEventListener", "resize", Callback.Create(() => Console.WriteLine("WindowResized")));
Create a new Javascript object
IJSInProcessObjectReference worker = JS.New("Worker", myWorkerScript);
ActionEvent
Used throughout the JSObject collection, ActionEvent allows a clean .Net style way to add and remove .Net callbacks for Javascript events.
With ActionEvent the operands += and -= can be used to attach and detach .Net callbacks to Javascript events. All reference handling is done automatically when events are added and removed.
Example taken from the Window JSObject class which inherits from EventTarget.
// This is how ActionEvent is implemented in the Window class
public ActionEvent<StorageEvent> OnStorage { get => new ActionEvent<StorageEvent>("storage", AddEventListener, RemoveEventListener); set { } }
Example event attach detach
void AttachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
// If this is the first time Window_OnStorage has been attached to an event a .Net reference is automatically created and held for future use and removal
window.OnStorage += Window_OnStorage;
// the window JSObject reference can safely be disposed as the .Net reference is attached to Window_OnStorage internally
}
void DetachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
// If this is the last reference of Window_OnStorage being removed then the .Net reference will automatically be disposed.
// IMPORTANT - detaching is important for preventing resource leaks. .Net references are only released when the reference count reaches zero (same number of -= as += used)
window.OnStorage -= Window_OnStorage;
}
void Window_OnStorage(StorageEvent storageEvent)
{
Console.WriteLine($"StorageEvent");
}
ActionEvent arguments are optional
Methods attached using ActionEvents are strongly typed and, like Javascript, all arguments are optional. This can improve performance as unused variables will not be brought into Blazor during the event.
Example event attach detach (from above) without using any callback arguments.
void AttachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
window.OnStorage += Window_OnStorage;
}
void DetachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
window.OnStorage -= Window_OnStorage;
}
// The method below is not using the optional StorageEvent argument
void Window_OnStorage()
{
Console.WriteLine($"StorageEvent");
}
ActionEvent.On() and ActionEvent.Off()
ActionEvent has additional methods for attaching event handlers; On and Off. These methods provide support attaching event handlers with alternative parameter types.
Or use the On and Off methods:
void AttachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
window.OnStorage.On(Window_OnStorage);
}
void DetachEventHandlersExample()
{
using var window = JS.Get<Window>("window");
window.OnStorage.Off(Window_OnStorage);
}
// The method below is not using the opt
