-
Notifications
You must be signed in to change notification settings - Fork 23
Utility methods
For working BrowserInterop needs some utilities that aim at simplifying JSInterop code. Some of these are publicly available and can also help you implement some of the missing pieces of BrowserInterop. All of these classes and extension methods are available in BrowserInterop.Extensions
When communicating browser information between JS and C# it's import to limit the quantity of information exchanged for 2 reason :
- The data is serialized as json with System.Text.Json and some object graph in the browser API might have loops ("window.self.self.self" for instance) that would make the serialization break
- Because the data is serialized as json, we want to limit the amount of data transmitted for performance reason
- Most of the fields in the Browser API are not serialized by JSON.stringify so when we send an object form js to c# we need to map it to a classic JS object, for making this code more efficient we ned to limit it somehow.
In many method of the Extensions API you will see a parameter "object serializationSpec". This object specifies that data needed from JS like this :
var serializationSpec = new {inner = "*", ignore = false, include = true}
It's a dynamic object that specifies to BrowserInterop what to do with each fields :
- "*" means 'Get everything inside this member, child, grand child, the whole family
- false means 'Do not get this field' (default)
- true means 'Get this fields value, not the childs'
Instead of the dynamic object you can send the following values :
- "*", null, true : get everything
- false : get nothing
This small class is used in a lot of place in BrowserInterop : it's a reference to a js object in the C# side so you can call method on them or get their content.
This extension method on IJSRuntime will return you a JsRuntimeObjectRef corresponding to the member of the window object
var windowMember = await jsRuntime.GetWindowPropertyRef("member");
This extension method on IJSRuntime will return you the content of the property of a js object for which you have a JsRuntimeObjectRef
var windowMember = await jsRuntime.GetInstanceProperty("member", new {field = "*", field2="*"});
This will give you the content of the member property (given the serializationSpec you will have the fields "field" and "field2" and all their childrens)
This extension method on IJSRuntime will set the value of a property of a js object for which you have a JsRuntimeObjectRef
await jsRuntime.SetInstanceProperty(windowRef, $"personalbar.visible", false);
This will set the value of the window.peronalbar.visible property to false;
This extension method on IJSRuntime will return you the JsRuntimeObjectRef to property of a js object for which you have a JsRuntimeObjectRef
var consoleRef = await jsRuntime.GetInstancePropertyRef(windowRef, $"console");
This will give you the JsRuntimeObjectRef for window.console
This extension method on IJSRuntime will call a method of a JS object
await jsRuntime.InvokeInstanceMethod(windowRef, "performance.clearMeasures", "test");
This will call "clearMeasures" on "window.performance" with the first parameter "test".
There is also an generic overload for getting the result of the call.
await JsRuntime.InvokeInstanceMethod<bool>(windowRef, "sendBeacon", url, data);
This method is a bit like InvokeInstanceMethod but you will get a JsRuntimeObjectRef referncing the result instead of the result itself, so you can call method on it later
var windowOpenRef = await JsRuntime.InvokeInstanceMethodGetRef(windowRef, "open", url);
This call will open a new window and return a reference to the newly opened window (so you can call "close" on it for instance).
This extension method on IJSRuntime will get the value of a js object for which you have a JsRuntimeObjectRef
var idleDeadline = await JsRuntime.GetInstanceContent<IdleDeadline>(jsRef, true);
JsRuntimeObjectRef is a IdleDeadLine for which you will receive all the content (serializationSpec = true)
This extension method on IJSRuntime will tell you if a JsRuntimeObjectRef has the given property/method, it's usefull for browser feature detection.
return await JsRuntime.HasProperty(windowRef, "canShare") && await JsRuntime.InvokeInstanceMethod<bool>(windowRef, "canShare", shareData);
This check if the current browser support the method "canShare" and then calls it.
This extension method on IJSRuntime will attach a listener on an event on a js object (for which you have a JsRuntimeObjectRef) and call the c# method you gave it.
return await JsRuntime.AddEventListener(windowRef, "", "languagechange", CallBackInteropWrapper.Create(() => {/** whatever, this is async **/}, false));
- This will call the lambda expression send to "CallBackInteropWrapper.Create" whenever the languagechange event is raised in the window object.
- This returns an AsyncDisposable that you need to dispose when you no longer whant to listen to the event
- The callback must return a ValueTask : make it async or return new ValueTask()
It's strongly advised to Dispose your JsRuntimeObjectRef so they are not kept forever in the browser memory
protected override async Task OnInitializedAsync()
{
var windowMember = await jsRuntime.GetWindowPropertyRef("member");
//the js object window.member is kept into an internal map on the js side until next line
await windowMember.DisposeAsync();
}
This base class can be used to implement C# wrapper class around JS class, for instance here is the class WindowVisualViewPort
public class WindowVisualViewPort : JsObjectWrapperBase
{
public int OffsetLeft { get; set; }
public int OffsetTop { get; set; }
public int PageLeft { get; set; }
public int PageTop { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public double Scale { get; set; }
public async ValueTask<IAsyncDisposable> OnResize(Func<ValueTask> todo)
{
return await JsRuntime.AddEventListener(JsObjectRef, "", "resize", CallBackInteropWrapper.Create(todo));
}
public async ValueTask<IAsyncDisposable> OnScroll(Func<ValueTask> todo)
{
return await JsRuntime.AddEventListener(JsObjectRef, "", "scroll", CallBackInteropWrapper.Create(todo));
}
}
And here is how we load it
return await JsRuntime.GetInstancePropertyWrapper<WindowVisualViewPort>(windowRef, "visualViewport");
This single method call will get the visualviewport fields (there is no serializationSpec so we get everything) and initialize the WindowVisualViewPort so we can later call interop method on if (here it's mostly event subscription)
For handling event subscription we created some way to pass a lambda expression to a js method. To do this instead of passing directly the lambda to your interop call you wrap it like this
CallBackInteropWrapper.Create(todo)
On the js side you will receive a js method that is a "link" to this lamba expression (more about it here). You can use this with the helper method from the previous chapter or with the other IJSRuntime methods.