How To Jsruntime

Published on Tuesday, January 02, 2024

How To: JSRuntime

There are quite a few resources for how to interop with JavaScript, not the least of which is ASP.NET Core Blazor JavaScript interoperability (JS interop).

However, I have run across some scenarios that are not covered there.

Consider if you really need JavaScript to accomplish your goal. Much in the way that you don't have to debug code you don't write, you don't have to troubleshoot JavaScript that can be replaced by native Blazor functionality. As you read, you will see how JavaScript functionality becomes more complicated.

The simplest, and least error-prone way to call JavaScript is to inject the JsRuntime service into your component via:

[Inject] public IJSRuntime JSRuntime { get; set; }

...and call it by:

await JSRuntime.InvokeVoidAsync("alert", "This is an alert");

This is free from trouble and worry, but it only works for methods that are built into the JavaScript runtime, so its usefulness is limited.

To introduce new JavaScript methods, you can load JavaScript files. These can be just a list of methods or formal JavaScript modules.

You can load JavaScript files either statically or dynamically. Statically loaded files go in your index.html file:

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">đź—™</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="scripts/yourJavaScriptLibrary.js"></script>
</body>

Now, the functions in that file are available.

Dynamically loaded files are loaded in the component's startup process; this is called "JavaScript Isolation". I have seen both OnInitializedAsync and OnAfterRenderAsync (with the "firstRender" flag) used: I suspect the latter is so that all DOM elements are available when the script is loaded. One the one hand, this means that you don't have to mess with the index file and pollute the window namespace, but your component should now implement IDisposable and dispose of the module field, lest your memory leak.

protected override async Task OnInitializedAsync()
{
  await base.OnInitializedAsync();
  _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/yourJavaScriptLibrary.js");
}

If your JavaScript wants to call your component's instance methods, you have to pass it a "DotNetObjectReference", hereafter a DNOR.

    private async Task JSCallsDotNetMethod()
    {
        var dotNetObjRef = DotNetObjectReference.Create(this);
        await JSRuntime.InvokeVoidAsync("lib.someMethod", dotNetObjRef);
    }        

This is the simple and easy case. However, some JavaScript methods want to store the DNOR, either explicitly in a JS variable, or implicitly, in a callback. It might be better to just create one instance of the DNOR and dispose it in the C# class’s Dispose method.

But wait! There’s another wrinkle. The JavaScript could well try to access the DNOR after it is disposed of, and throw an exception, albeit a seemingly harmless one, but I hate having to decide if an exception is okay or not. Therefore, I recommend that you add a “setDotNetReference” method to and a DNOR field to the JS Module, call it to set the DNOR, add a call to that method in the Dispose method to set the DNOR to null, and check for null in the JS before using the DNOR. inhales

Or... do you really have to use JavaScript?