Ed Charbeneau Ed Charbeneau is a Sr. Developer Advocate for #Blazor & Telerik products at Progress and Microsoft MVP. http://edcharbeneau.com

Working with the Blazor JavaScript Interop

4 min read 1182


In this article, we’ll look at Blazor, a single-page web app framework built on .NET that runs in the browser with WebAssembly. We’ll get an understanding of how Blazor handles JavaScript calls, why it’s necessary, and how it’s used.

As WebAssembly ( abbreviated Wasm) is gaining momentum it’s important to understand the current capabilities. WebAssembly lacks the ability to directly access the Browser’s DOM API, however, it can make calls to JavaScript. Because of this shortcoming JavaScript is still very much a part of web development.


Blazor, Mono, and WebAssembly

WebAssembly (Wasm) is a binary instruction format that is designed to provide a compilation target for high-level languages like C#. Recently Microsoft began experimenting with WebAssembly to bring .NET to the browser using the Mono run-time. Mono provides the basic plumbing allowing .NET libraries (.dll’s) to run on WebAssembly.

A block diagram of the Blazor & Browser relationship

Blazor features a component architecture, routing, a virtual DOM, and a JavaScript Interoperability (interop) API. Through the JavaScript interop a Blazor app can invoke JavaScript functions from .NET and C# methods from JavaScript code.
 
To call into JavaScript from .NET the IJSRuntime abstraction is used. The current instance of IJSRuntime is resolved by making a call to JSRuntime.Current. From this instance we can call the InvokeAsync<T> method passing in the first argument as an identifier to the corresponding JavaScript function we would like to invoke, this function must be available on the global scope of window. Additional arguments may be passed through to the JavaScript function provided they are JSON serialize-able as well as the return type Task<T>.

using Microsoft.JSInterop;
public class ExampleJsInterop
{
  public static Task<T> MethodName(TArgs args)
  {
    // Implemented in exampleJsInterop.js
    return JSRuntime.Current.InvokeAsync<T>("scope.jsMethod", args);
  }
}

JavaScript Interop

Since Blazor is built upon Mono and WebAssembly and therefore has no direct access to the browser’s DOM API, it must marshal calls through JavaScript when it needs DOM access. The inclusion of JavaScript in the stack is not only beneficial in terms of necessity, but also flexibility.
 
Backwards compatibility
 
Including JavaScript in the stack enables Blazor applications to utilize existing JavaScript libraries. This includes UI libraries like Bootstrap, Toastr.js, a toast notification library, and Chart.js for simple charting components.

In addition, full-featured commercial UI libraries such as Kendo UI could potentially be ported to Blazor. These “ports” essentially provide a C# API surface for interacting with the underlying JavaScript while providing a migration path for users.
 
Mind the gap
 
Because Blazor is new and experimental, the interop allows developers to fall back on JavaScript when there are shortcomings of WebAssembly itself, or because the Blazor framework is not yet mature.
 
For example, if we wanted to use a standard window prompt() method, there is no native support in Blazor to do this. However, a simple API can be created using the JavaScript interop to add support for this functionality.
 
We’ll start by creating a JavaScript file with the method we would like to invoke from our application.

For the function to be visible to Blazor, we’ll need to add it to the scope of window. As a best practice, additional namespaces can be added using a module pattern, this protects our methods from conflicting with other code on the scope of window. Within our namespace, we define a function to call the native window prompt() method.

window.myNamespace = {
    showPrompt: function (message) {
    return prompt(message, 'Type anything here');
  },
    anotherFunction: function(args) { 
    // do stuff 
  }
};

Next, we need to invoke the JavaScript showPrompt function from within C# using the JSRuntime.Current.InvokeAsync method. A C# function PromptAsync provides a nice abstraction that can be used within the Blazor application. Developers using the abstraction will not need to understand the underlying JavaScript implementation.

using Microsoft.JSInterop;

public class PromptInterop
{
    /// <summary>
    /// Invokes a browser prompt and returns the user's input.
    /// </summary>
    public static Task<string> PromptAsync(string message) {
        return JSRuntime.Current.InvokeAsync<string>("myNamespace.showPrompt",message);
	}
}

Since Blazor’s UI process is capable of running on a separate thread from the application InvokeAsync should be used by default.

However, if there is a need to invoke the JavaScript method synchronously, we can provide that functionality by downcasting JSRuntime to IJSInProcessRuntime. Adding the Prompt method in addition to PromptAsync provides an alternative API when asynchronous behavior is not available.

using Microsoft.JSInterop;

public class PromptInterop
{
    /// <summary>
    /// Invokes a browser prompt and returns the user's input.
    /// </summary>
    public static Task<string> PromptAsync(string message) {
        return JSRuntime.Current.InvokeAsync<string>("myNamespace.showPrompt",message);
	}

    /// <summary>
    /// Syncronously invokes a browser prompt and returns the user's input. Use for in-process-senarios only.
    /// </summary>
    public static string Prompt(string message) {
        return ((IJSInProcessRuntime)JSRuntime.Current).Invoke<string>("myNamespace.showPrompt",message);
	}
}

The ShowPrompt method is now available to use within the application. We can call PromptAsync from a Blazor component by calling the method and awaiting a result.

In the following example, we’ll trigger a browser prompt when the user clicks on the component. When the prompt is closed the result is returned to the component’s Message field which is data-bound and rendered to the component. To ensure the new value is updated when data-binding occurs, we’ll call StateHasChanged to instruct Blazor to re-render the component.

<div onclick="@HandleClick" class="my-component">
    @Message
</div>

@functions {
    string Message = "Click to change";
    async void HandleClick()
    {
        Message = await PromptInterop.PromptAsync("Type a message");
        StateHasChanged();
    }
}

Blazor JavaScript interop providing a browser prompt

Conclusion

While Blazor and WebAssembly lack the ability to directly access the Browser’s DOM API, the JavaScript interop provides a means of filling the gap. The interop makes it possible to migrate existing JavaScript libraries to Blazor. Through the interop developers can create abstractions around browser features providing C# methods to add functionality at the application level.

As Blazor gains in popularity, it is reasonable to assume that an ecosystem of interop libraries will emerge. As more interop libraries become available, then Blazor developers may spend less time writing JavaScript and more time in C#.

Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.

Ed Charbeneau Ed Charbeneau is a Sr. Developer Advocate for #Blazor & Telerik products at Progress and Microsoft MVP. http://edcharbeneau.com

Leave a Reply