Skip to content

Utility methods

Rémi BOURGAREL edited this page Jul 17, 2020 · 3 revisions

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

SerializationSpec

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

JsRuntimeObjectRef

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.

GetWindowPropertyRef

This extension method on IJSRuntime will return you a JsRuntimeObjectRef corresponding to the member of the window object

    var windowMember = await jsRuntime.GetWindowPropertyRef("member");

GetInstanceProperty

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)

SetInstanceProperty

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;

GetInstancePropertyRef

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

InvokeInstanceMethod

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);

InvokeInstanceMethodGetRef

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).

GetInstanceContent

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)

HasProperty

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.

AddEventListener

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()

DisposeAsync

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();
}

JsObjectWrapperBase

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)

JsInteropActionWrapper

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.