In this article, we are going to learn how to call JavaScript functions with C# methods in our Blazor WebAssembly application. We are going to cover different situations and different ways to interact with the JS code from our C# classes.
If you want to see complete navigation for this series, you can visit our Blazor WebAssembly Page and find all of the articles from this series and many other articles as well.
So, let’s dive right into the business.
For this article and the others in JS Interop with the .NET series, we are going to use the .NET 5 framework RC2 and Visual Studio 16.8.0 to support that.
So, let’s start by creating a new Blazor WebAssembly project.
With that out of the way, we are going to remove all the links except the Home
link from the NavMenu
component:
<div class="@NavMenuCssClass" @>After that, let’s create two new files (
CallJavaScriptInDotNet.razor
andCallJavaScriptInDotNet.razor.cs
) in thePages
folder:Don’t forget to make the class partial.
Also, we are going to add a route to this component:
@page "/jsindotnet" <h3>Call JavaScript In DotNet</h3>Finally, let’s modify the Index component:
@page "/" <h3> Use the following links to explore different examples of using a JS code with .NET in Blazor WebAssembly Project: </h3> <ul> <li> <a href="/jsindotnet"> How to call JavaScript code from .NET </a> </li> </ul>This is going to be the starting point for our different examples. And we’ll store each section in a different component and create a new navigation link to it.
Now, we can move on.
Call JavaScript Functions from C# when JS Functions Return Void
Depending on the return type of our JavaScript functions, the code is going to be a bit different in our C# classes. So, we are going to explore different ways of calling JavaScript functions from C# in our examples.
The first thing we are going to do is to create a new
.js
file in thewwwroot/scripts
folder and name itjsExamples.js
:Now, let’s create a simple function that shows the alert message in our browser:
function showAlert(message) { alert(message); }This is a simple function that triggers the Javascript
alert
function, which shows the alert window with a custom message. Pay attention that with a function creation like this one, we are storing ourshowAlert
function in the globalwindow
namespace. In the next section, we are going to see how to create a function but without storing it in the global window namespace.Now, let’s import this file in the
index.html
file to be available in our project:<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/jsExamples.js"></script> </body>After that, let’s add new content in the
CallJavaScriptInDotNet.razor
file:<div class="row"> <div class="col-md-4"> <h4> Example for calling a JS function returning void: </h4> </div> <div class="col-md-6"> <button type="button" class="btn btn-info" @>This is just a simple HTML to help us with the example.
Now, in the
CallJavaScriptInDotNet.cs
file, we are going to create theShowAlertWindow
method:public partial class CallJavaScriptInDotNet { [Inject] public IJSRuntime JSRuntime { get; set; } public async Task ShowAlertWindow() { await JSRuntime.InvokeVoidAsync("showAlert", "JS function called from .NET"); } }Here, we first inject the
IJSRuntime
service, which we are going to use to invoke JavaScript functions. As you can see we are using the[Inject]
attribute. For this to work we have to add two using directives:using Microsoft.AspNetCore.Components; using Microsoft.JSInterop;Then, in the
ShowAlertWindow
method, we use the injected service to call theInvokeVoidAsync
method. We have to pass theidentifier
, which is the name of the method, and themessage
parameter.Now, we can start our app, navigate to this component, and press the button:
As you can see, we are able to call JavaScript functions with .NET with the small help of
IJSRuntime
service.JavaScript Isolation in Blazor WebAssembly
From the .NET 5 (RC 1) version, we are able to isolate our JavaScript code as a standard JavaScript module.
This is beneficial because
index.html
fileSo, let’s see how we can implement this in our example.
First, let’s remove the script code <script src="scripts/jsExamples.js"></script>
from the index.html
file.
Then, we have to modify our function in the jsExample.js
file:
export function showAlert(message) { alert(message); }
We use the export
keyword to export this function from this file.
After that, we can modify the CallJavaScriptInDotNet.razor.cs
file:
public partial class CallJavaScriptInDotNet { [Inject] public IJSRuntime JSRuntime { get; set; } private IJSObjectReference _jsModule; protected override async Task OnInitializedAsync() { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/jsExamples.js"); } public async Task ShowAlertWindow() { await _jsModule.InvokeVoidAsync("showAlert", "JS function called from .NET"); } }
In the OnInitializedAsync
lifecycle method, we import our JavaScript module with the help of JsRuntime
service. The import
identifier is a special identifier that we use to import JS modules. Then, we can use the IJSObjectReference
variable in the ShowAlertWindow
method to call the function from our module.
Now, if we start our app one more time, navigate to the component and click the button, we are going to see the alert window again.
So with this approach, we can load the JS module when we need it and use it where we need it.
While calling the showAlert
function, we are passing a simple string message. But this communication supports more complex types as well.
To test this out, let’s modify the ShowAlertMessage
method:
private async Task ShowAlertWindow() => await _jsModule.InvokeVoidAsync("showAlert", new { Name = "John", Age = 35 });
Also, we are going to modify the showAlert
function:
export function showAlert(obj) { const message = 'Name is ' + obj.name + ' Age is ' + obj.age; alert(message); }
Now, we can repeat our testing steps and notify that our anonymous object was successfully sent to the JavaScript function:
Good.
Now, we can move on.
Up until now, we have seen how to call JavaScript functions with C# methods, when those JS functions don’t return a result. But of course, we don’t write only void functions, many of those return some values. So, let’s see how we can invoke these functions as well.
Let’s open our jsExample
file, and add one more function:
export function emailRegistration(message) { const result = prompt(message); if (result === '' || result === null) return 'Please prvode an email' const returnMessage = 'Hi ' + result.split('@')[0] + ' your email: ' + result + ' has been accepted.'; return returnMessage; }
There’s nothing special in this snippet. We show the prompt with the message and accept the user’s response. If it is an empty string or null result we just return a default message. Otherwise, we create a return message and return it back to the .NET part of the application.
Now, let’s add a new HTML markup code in the CallJavaScriptInDotNet.razor
file:
<div class="row"> <div class="col-md-4"> <h4> Example for calling a JS function returning result: </h4> </div> <div class="col-md-2"> <button type="button" class="btn btn-info" @>Of course, we need to create our C# logic:
public partial class CallJavaScriptInDotNet { [Inject] public IJSRuntime JSRuntime { get; set; } private IJSObjectReference _jsModule; private string _registrationResult; ... private async Task RegisterEmail() => _registrationResult = await _jsModule.InvokeAsync<string>("emailRegistration", "Please provide your email"); }This time, we use the
InvokeAsync<string>
method to call the JavaScript function that returns a string. Then, we just pass an identifier and an additional parameter. Also, we store the result in the_registrationResult
field, which we show on the page.To test this out, let’s start our app, navigate to the component, and click the
Register Email
button:Once we click the
OK
button:We can see the result on the right.
Note About Different Types
So, as you can see, we have to provide a type for the
InvokeAsync
method that corresponds to the return type from our JS function. Obviously, if our JS function returns an int or a boolean, we have to provide the appropriate type for theInvokeAsync
function. That said, the same applies to objects. For example, if our JS function returns an object containing properties of theUser
class, the call to that function would beawait _jsModule.InvokeAsync<User>
whereUser
is our C# class.This means that Blazor automatically deserializes our JS object to a C# object, which is great.
We can see this in action if we create a new function in a .js file:
export function splitEmailDetails(message) { const email = prompt(message); if (email === '' || email === null) return null; const firstPart = email.substring(0, email.indexOf("@")); const secondPart = email.substring(email.indexOf("@") + 1); return { 'name': firstPart, 'server': secondPart.split('.')[0], 'domain': secondPart.split('.')[1] } }Here, we extract different parts of an email address and return a new object with these properties. Of course, we assume that a user provides a valid email address (the validation is out of the scope of this article).
After that, we are going to create our EmailDetails helper class:
public class EmailDetails { public string Name { get; set; } public string Server { get; set; } public string Domain { get; set; } }Then, let’s create a new method in our component’s class file:
public partial class CallJavaScriptInDotNet { ... private string _detailsMessage; ... private async Task ExtractEmailInfo() { var emailDetails = await _jsModule.InvokeAsync<EmailDetails>("splitEmailDetails", "Please provide your email"); if (emailDetails != null) _detailsMessage = $"Name: {emailDetails.Name}, Server: {emailDetails.Server}, Domain: {emailDetails.Domain}"; else _detailsMessage = "Email is not provided."; } }In this method, we call the JS function and store the returned object inside the
emailDetials
variable. Then, we just populate the_detailsMessage
field.Finally, we have to add a new HTML markup:
<div class="row"> <div class="col-md-4"> <h4> Calling a JS function that returns an object: </h4> </div> <div class="col-md-2"> <button type="button" class="btn btn-info" @>And that’s it.
We can start our application, navigate to the component, and click the
Email Details
button. Once the popup appears, let’s enterjohndoe@gmail.com
. After we click theOK
button:We can see the result.
Conclusion
So, now we know how to call JavaScript functions with C# methods using JSInterop features. Also, we have learned how to isolate our JavaScript functions and how to register JS modules without using the
<script>
element in theindex.html
file. For now, we only have communication from .NET methods to JS functions, but, we will also cover communication the other way around in one of our next articles from this series.In the next article, we are going to talk about using JSInterop with Blazor WebAssembly Lifecycle, how to pass HTML elements to JS functions, and how to handle JS errors.
See you there.
Issue #134 of the Code Maze weekly. Check out what's new this week and enjoy…
In this article, we'll look at the heap sort algorithm in C#. We'll discuss what…
In this article, we are going to learn about the Flags attribute for enum in…
Issue #133 of the Code Maze weekly. Check out what's new this week and enjoy…
In this article, we are going to learn various ways of converting a value from…
In this article, we are going to learn how to sort the values in the…
View Comments
Well, how would you import node_module libraryies into theese JS files? Im getting all kinds of errors and would like help if possible. Ive tried a bunch.
To be honest I didn't do that at all, so I'm not sure how it works. Can this maybe help https://nbarraud.github.io/js-in-blazor.html ?
Thanks, ill take a look. I know you didn't, i just worded my question badly - my bad. Thanks alot for the nice read though.
Thanks, That was great
Hello,
many Thanks for this explanation. It helps a lot! But what if the Javascript funtion requires two parameters? Is that possible too?
Best Regards
Sascha
Hello Sascha. Thanks a lot for those kind words.
If you inspect the InvokeAsync's signature, you'll see that it accepts an array as a second parameter:
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue> (string identifier, object?[]? args);
So, you can create an array of parameters and send them as a second parameter, and then split them in the JS function.