Use JavaScript Interop in Blazor

ASP.NET Core Blazor allows you build SPAs using C#, HTML, and CSS. Although you can develop Blazor apps without using any JavaScript, at times you might want to invoke some JavaScript code from your Blazor components. Moreover, the JavaScript code might also want to invoke some C# methods. To that end this article quickly illustrates how this can be accomplished using Blazor's JavaScript interop features.

To begin, create a new Blazor Server app using Visual Studio.

Once the app is created, add a new folder under wwwroot named Scripts. And also add a new JavaScript file in it named JavaScript.js. This JS file contains our code that we want to use with Blazor JS interop.

Now add the following JavaScript function in the JavaScript.js file.

function GetMessage(target) {
    return "Hello " + target + "!!!";
}

The GetMessage() JavaScript function accepts a target parameter, appends it to a Hello message, and returns the resultant string back to the caller.

Next, open _Host.cshtml file and add a <script> reference in it's head section as shown below:

<script src="/scripts/JavaScript.js"></script>

We will now invoke this JS function from a Blazor component.

Add a new Razor Component named JsInterop.razor in the Pages folder.

Then write the following code in the newly added JsInterop.razor component.

@page "/JsInterop"

@inject IJSRuntime JS

Here, we set the component's route to /JsInterop so that the component can be accessed as a standalone page in the browser. We then injected a IJSRuntime object into the component. The IJSRuntime object represents the JavaScript runtime and can be used to invoke JavaScript code. In our code we will access this IJSRuntime injected object using the JS property (you can give any other name to this property as per your choice).

Next, add the following markup to the component.

<h3>JS Interop Demo</h3>

<h5>@Message</h5>

<button id="button1" @onclick="OnButton1Click">
  Click to invoke JS function
</button>

This markup outputs the Message property in the page. The Message property will be added in the @code block shortly and will be assigned a value in the click event handler of the button. The Message property and the OnButton1Click() event handler function is shown below.

@code {
  
  public string Message { get; set; }

  public async void OnButton1Click(MouseEventArgs e)
  {
    try
    {
	Message = await JS.InvokeAsync<string>
("GetMessage", "World");
    }
    catch(JSException ex)
    {
	Message = ex.Message;
    }
    StateHasChanged();
}

The OnButton1Click() event handler invokes the GetMessage() JavaScript function using InvokeAsync() method of IJSRuntime object. The InvokeAsync() method takes a generic return type (string in this case), a JS function name to invoke (GetMessage in this case), and a list of function parameters. In this case GetMessage() accepts only a single parameter and we pass "World" as a value for this parameter. GetMessage() returns a string value and it is assigned to the Message property.

The call to InvokeAsync() is wrapped inside a try-catch block because there could be some error while calling the JS function. For example, the target function might be missing in the JS code. If there is any error InvokeAsync() throws JSException. We display the error message using the Message property.

Run the application and navigate to /JsInterop. You should see the component as shown below.

If you click the button you should see the message - Hello World !!!.

Now, come back to the InvokeAsync() call and deliberately specify some wrong JS function.

Message = await JS.InvokeAsync<string>("GetMessage123", "World");

Run the app again, click on the button, and see the JSException getting thrown.

In the preceding example, we invoked the GetMessage() function from the click event handler of the button. What if you want to invoke it as soon as the component is displayed in the browser. One obvious place that comes to mind is Blazor's OnInitialized() life cycle method.

protected async override void OnInitialized()
{
	Message = await JS.InvokeAsync<string>
  ("GetMessage", "Galaxy");
}

However, this won't work as expected and you will end up getting this error.

The error clearly tells us that JS interop calls can't be added in the OnInitialized() and the recommended place to add them is OnAfterRenderAsync(). So, let's fix this error by following the remedy.

protected override async void OnAfterRenderAsync(bool firstRender)
{
  if(firstRender)
  {
    Message = await JS.InvokeAsync<string>
("GetMessage", "Galaxy");
    StateHasChanged();
  }
}

This time we placed the InvokeAsync() call inside OnAfterRender() method. And it works as expected.

The OnAfterRenderAsync() method also comes handy when you want to add mouse-over effect or any such code. Let's see an example that uses jQuery to add mouse-over effect to a button.

Firstly, add jQuery library file to the www/Scripts folder.

Then add a <script> reference to the _Host.cshtml file.

<script src="/scripts/jquery-2.1.1.js"></script>

Next, open JavaScript.js file and write the following function in it.

function WireJQueryEventHandler() {

    $("#button1").mouseenter(function (e) {
        $(this).css("background-color", "lightgreen");
    });

    $("#button1").mouseover(function (e) {
        $(this).css("background-color", "lightgreen");
    });

    $("#button1").mouseleave(function (e) {
        $(this).css("background-color","");
    });
}

The WireJQueryEventHandler() function uses jQuery to handle mouseenter, mouseover, and mouseleave events. These event handlers simply set or remove the background-color CSS property.

After writing the WireJQueryEventHandler() function add the following code in the JsInterop.razor component.

protected override async void OnAfterRender(bool firstRender)
{
  if(firstRender)
  {
    Message = await JS.InvokeAsync<string>
("GetMessage", "Galaxy");
    await JS.InvokeVoidAsync("WireJQueryEventHandler");
    StateHasChanged();
  }
}

Since WireJQueryEventHandler() function doesn't return any value to the caller, we use InvokeVoidAsync() method of IJSRuntime.

Run the app and see the mouse-over effect in action.

In this example we used jQuery library to add mouse-over effects. If you modify the DOM from JS code, Blazor won't be able to automatically detect the changes. It is recommended that you avoid modifying DOM structure or content from JS code. You may read this thread to understand the problem and a possible solution.

In the preceding examples we invoked JS code from C#. What if we want to invoke C# code from JS? Luckily, Blazor covers that possibility also. Let's see how.

Let's add another <button> element to the JsInterop.razor component.

<button id="button2" @onclick="OnButton2Click">
Click to invoke C# method</button>

Now write the click event handler OnButton2Click() in the @code block as shown below:

public async void OnButton2Click()
{
  var flag = await JS.InvokeAsync<bool>("CallCSMethod", 
DotNetObjectReference.Create(this));

	StateHasChanged();
}

This code should look familiar to you because it resembles previous example. However, this time we call a JavaScript function named CallCSMethod(). In order to call a C# method residing in our Razor Component, the CallCSMethod() function will need a reference to the component. This object reference is obtained using DotNetObjectReference class. The Create() static method of DotNetObjectReference accepts the component instance and returns a DotNetObjectReference instance. The CallCSMethod() function returns a Boolean value that is stored in a local variable (although not used in the code).

Before we write the CallCSMethod() JavaScript function, let's add a C# method that we want to call from the JavaScript code. So, add the following method in the @code block of JsInterop.razor.

[JSInvokable]
public bool SetMessage(string msg)
{
  Message = msg;
  StateHasChanged();
  return true;
}

The SetMessage() method accepts a msg parameter that will be passed from the JS code. Inside, we assign the supplied message to the Message component property and return true to the caller. What makes SetMessage() special is the [JSInvokable] attribute. This attribute makes the underlying C# method JavaScript callable.

Now open JavaScript.js file and add the CallCSMethod() function as shown below.

function CallCSMethod(compRef) {

    alert("You will be calling a C# method!");

    compRef.invokeMethodAsync("SetMessage",
        "Hello Universe!!!").then((result) => {
            if (result) {
                alert("C# method was successful!");
            }
        });

    return true;
}

Notice the code shown in bold letters. The CallCSMethod() accepts the DotNetObjectReference in the compRef parameter. Inside, we first show a JS alert telling the user that a C# method is being called. Then comes the important piece of code. The invokeMethodAsync() method of the compRef is used to invoke the SetMessage() C# method. The first parameter to invokeMethodAsync is a C# method name to be called followed by parameter(s) to the method.

The invokeMethodAsync method returns a JavaScript Promise. We handle the successful call of the method by wiring the then() callback function. The result parameter to the success function is the return value of SetMessage() method (true in this case). We show that value in another alert box.

If you run the application you will see the newly added button like this:

Clicking on the second button will result in displaying of the first alert box confirming that CallCSMethod() has been called.

Clicking on the OK will continue the execution and SetMessage() will be called changing the message to Hello Universe!!!

And finally showing the second alert.

In the preceding example the SetMessage() was an instance method. You can also call static methods from the JavaScript code. Of course, you can't access component properties inside these static methods. Let's see an example of calling a static method also.

Add another C# method in the JsInterop.razor file as shown below:

[JSInvokable]
public static string GetServerData()
{
  return "Hello from C#!!!";
}

The GetServerData() static method is quite straightforward and simply returns a string to the JS code. To invoke GetServerData() method from the CallCSMethod() JavaScript function add this code:

function CallCSMethod(compRef) {

    alert("You will be calling a C# method!");

    compRef.invokeMethodAsync("SetMessage",
        "Hello Universe!!!").then((result) => {
            if (result) {
                alert("C# method was successful!");
            }
        });

    DotNet.invokeMethodAsync("JSInteropDemo",
        "GetServerData").then((result) => {
            if (result) {
                alert(result);
            }
        });

    return true;
}

Since GetServerData() is a static method we can't access it using the DotNetObjectReference object. Instead, we use DotNet inbuilt object and call  invokeMethodAsync() on it. The first parameter to invokeMethodAsync() is the name of assembly containing the static method. This will be typically your project name. The second parameter is name of the method to be called. The success callback is wired as before.

If you run the application again you will see the third alert as shown below.

I hope you got some idea of Blazor's JavaScript interop features. You may go here and here to know more. 

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, and meditation teacher. He has been programming, meditating, and teaching for 25+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 04 August 2021


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio