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!!