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

3 min read 1051


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.

We made a custom demo for .
No really. Click here to check it out.

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

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution 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.

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

    Leave a Reply