Blazor Server – Basics Part 8 – JavaScript interoperability (JS interop)
In this post I want to show how Blazor Server can interact with Java Script.
A Blazor app can invoke JavaScript (JS) functions from .NET methods and .NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).
Introduction
As mentioned in the article below from Microsoft, you should prefer to modify or manipulate the document object model (DOM) with Blazor instead of using Java Script.
Interaction with the Document Object Model (DOM)
Source: https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-7.0#interaction-with-the-document-object-model-dom
The reason for is, that everytime Blazor Server is sending the rendered HTML document to the clients browser, it is also making a virtual snapshot from the document object model (DOM) which is called virtual DOM.
Document Object Model (DOM)
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
Blazor Server using this virtual DOM to determine changes in the real DOM (web app running in the browser) and will only send the changes instead of the whole page (razor component) to the users browser. These changes will then be updated in the real DOM by the blazor.server.js framework as mentioned in Part 1.
If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor’s internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.
Nevertheless to create highly responsive interfaces like desktop apps with Blazor Server, you will not be able to get around of using JS Interop.
To solve the problem that Blazor Server will recognize and update changes made to the DOM by using JS Interop, you can call the C# method StateHasChanged(), which will notify the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered.
ASP.NET Core Razor component lifecycle
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0#state-changes-statehaschanged
Create a new Page which is using JS Interop
To show JS Interop in action, I will extend my time tracking and vacation planning web app with an options page, where the users can adjust some personal settings.
The users have assigned projects where they need to book the time they spent for. These projects can be selected on several pages by using a select box or a hybrid of a textbox and <select> box. It’s jQuery-based and it’s useful for tagging, contact lists, country selectors, and so on.
More about under the following link and further below.
Selectize
https://selectize.dev/
In order to simplify the project selection, the users should be able to set on this new options page two projects they mostly need to select and therefore will be added to their default projects, which will be displayed on the pages for simple selection like shown below.
The users then just needs to click on the project button and don’t have to use the select box which here by the way is the above mentioned hybrid of a textbox and <select> box. Later more about that.
Install NuGet Packages
Now back to our new options page.
First I will need to install some NuGet packages.
The System.Data.SqlClient package I will need to install because my web app is using a Microsoft SQL Server 2019 database to store its data.
Further I need to install the Newtonsoft.Json package because the Java Script functions which will handle the mentioned hybrid of a textbox and <select> box (Selectize), will pass the selected data as JSON object to my .NET C# methods in Blazor. More about further down in this post.
The {ARGUMENTS} placeholder in Java Script functions are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
Prepare the new Page
The next step is to add some @using directives for the new packages (.NET classes) in order to use the new class types without specifying the fully qualified namespace of that type.
We also need to inject some classes which is called dependency injection in order to use these classes (services) in our web app.
The options page should also use the custom layout template, therefore I will need to add the following directive.
@layout BCLayout
More about custom layouts you will find in Part 3.
@using BlazorApp1.Data will include the namespace from all classes (services) from my web app within the Data folder shown below.
@inject Database DBConnect will inject my custom Database.cs class which will handle accessing the Microsoft SQL Server database.
@inject BcActiveDirectory BcActiveDirectory is my custom BcActiveDirectory.cs class where I will request user information as shown in Part 6.
@inject IJSRuntime JS will inject the IJSRuntime class (service) which represents an instance of a Java Script runtime and we need to use for JavaScript interoperability (JS interop) for making method calls between .NET and JavaScript in Blazor applications.
@inject LoginState logState will inject a cutom class (service) where I will handle the state if a user is already signed-in to the web app or not.
@inject NavigationManager NavManager will inject the NavigationManager class which provides an abstraction for querying and managing URI navigation.
Before I will show how to making method calls between .NET and JavaScript, I will first add an icon to the LoginDisplay.razor component where I can open the options page later.
Here I will add a button and by using the <i> tag replace it with an icon. You could also use a <span> tag therefore.
For the icon I need to add the fa-solid fa-gears css class to the <i> tag.
When a user is clicking on the button (icon), the OpenUserSettings method will be executed.
In the _Host.cshtml file I need to add the following Java Script file in order the icon is displayed in my above tag. More about you will find under the following link https://fontawesome.com/
The OpenUserSettings method is using the NavigationManger class as mentioned previously to route the user to the options page.
Here you can see my button with the gear icon from fontawesome.com to open the options page.
Set up JS interop for the Page
For the above <select> boxes I will use as mentioned a hybrid of a textbox and <select> box which is jQuery-based.
Selectize
https://selectize.dev/
In my options razor page therefore I need to insert a <select> html tag and further I need to assign an id, here named SelProjekteOptionsOPT1.
To access and manipulate the <select> html element later, we can use in jQuery the #<myID> selector, therefore we first need to assign an id to the element here.
Selecting Elements in jQuery
https://learn.jquery.com/using-jquery-core/selecting-elements/
jQuery should manipulate the <select> html element after the page is rendered from Blazor Server and send to the users browser.
Next in the razor code block @code{ … } we need to add the following code to manipulate the <select> html element with jQuery after the page is rendered from Blazor Server.
The following Java Script function call we place in the OnAfterRender() method.
await JS.InvokeVoidAsync(“renderjQueryComponentsOptionsSite”);
Call JavaScript functions from .NET methods in ASP.NET Core Blazor
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-7.0
More about async methods and the await expression you will find in my following post.
In the OnAfterRender() method we also need to call the method below and shown in the screenshot above.
// Send Class Instance to JavaScript
await BundleAndSendDotNetHelper();
This method will in turn call the BundleAndSendDotNetHelper() method also shown below which will then send an instance of our razor options page to Java Script in order to be able to access and manipulate it later.
The .NET BundleAndSendDotNetHelper() method will call the Java Script function SetDotNetHelper in my braincourt.js file shown below.
Finally this stores the class instance we’ve passed from C# in a global variable for using it later in some other Java Script functions to access and manipulate the .NET classes.
Pass a DotNetObjectReference to a class with multiple JavaScript functions
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-7.0#pass-a-dotnetobjectreference-to-a-class-with-multiple-javascript-functions
So but now back to our first called Java Script function above JS.InvokeVoidAsync(“renderjQueryComponentsOptionsSite”) when the page is rendered, the following Java Script (jQuery) function is called directly from .NET by using the InvokeVoidAsync method from the Java Script runtime (IJSRuntime) we injected to our razor page options. The Java Script function is in my custom Java Script file braincourt.js as shown below.
For the selectize hybrid <select> and text box I will use the properties sortField, plugins and onChange. More information about all available properties and how to use the element you will find at the following link https://selectize.dev/docs/usage.
function renderjQueryComponentsOptionsSite() { $('#SelProjekteOptionsOPT1').selectize({ sortField: 'text', plugins: ['remove_button'], onChange: eventHandlerOPT1ProjektChanged('onChange'), }); $('#SelProjekteOptionsOPT2').selectize({ sortField: 'text', plugins: ['remove_button'], onChange: eventHandlerOPT2ProjektChanged('onChange') }); }
During manipulating the element with jQuery, we assign to the onChange property of the html select element, a Java Script function called eventHandlerOPT1ProjektChanged.
If now a user is changing the selected item, a .NET method in the razor page options named OnProjekteOPT1Change is called and also the selected project id is passed as JSON object to it.
The .NET method which is called from Java Script must be public, static, and have the [JSInvokable] attribute. You can also use a Task which I will use here instead of a method.
DotNet.invokeMethodAsync (Recommended): Asynchronous for both Blazor Server and Blazor WebAssembly apps.
As mentioned I could also use a method instead of a Task like shown below.
Because the arguments from the Java Script function will be passed as JSON object, we previously installed the Newtonsoft.Json package in order to access and extract the submitted Project_ID, which was selected in our hybrid <select> and text box in order to save it as a default project for simplified selection.
One last point to mention about my custom Java Script file braincourt.js, this is stored in the wwwroot folder and a dedicated folder for Java Script files named js.
The braincourt.js file then is added to my web app within the _Host.cshtml file.
Place the JavaScript (JS) tags (<script>…</script>) just before the the closing </body> tag rather than in the <head> section.
When the browser loads a website, it loads besides the HTML code also linked files like images, CSS and JavaScript files. Modern browsers could download multiple files at once, but when the browser detects a <script> object it starts immediately to process the code by using the JavaScript interpreter.
This will result in pausing the parsing and downloading of all other files and HTML code which will interrupt the page loading.
Further in case your JavaScript code wants to access specific HTML elements from the Document Object Model (DOM), they not loaded so far, you will run into an access error.
That’s it! We saw in this post how we can call JavaScript (JS) functions from .NET methods and .NET methods from JS functions which is called JavaScript interoperability (JS interop).
Blazor Server Series
Blazor Server – Basics Part 1
https://blog.matrixpost.net/blazor-server-basics-part-i/Blazor Server – Basics Part 2
https://blog.matrixpost.net/blazor-server-basics-part-ii/Blazor Server – Basics Part 3 – Custom Layout
https://blog.matrixpost.net/blazor-server-basics-part-iii-custom-layout/Blazor Server – Basics Part 4 – Program.cs File
https://blog.matrixpost.net/blazor-server-basics-part-iv-program-cs-file/Blazor Server – Basics Part 5 – Authentication and Authorization
https://blog.matrixpost.net/blazor-server-basics-part-v-authentication-and-authorization/Blazor Server – Basics Part 6 – Query the on-premise Active Directory
https://blog.matrixpost.net/blazor-server-basics-part-vi-query-the-on-premise-active-directory/Blazor Server – Basics Part 7 – C# Events, Delegates and the EventCallback Class
https://blog.matrixpost.net/blazor-server-basics-part-vii-c-events-and-delegates/Blazor Server – Basics Part 8 – JavaScript interoperability (JS interop)
https://blog.matrixpost.net/blazor-server-basics-part-viii-javascript-interoperability-js-interop/Blazor Server – Basics Part 9 – Responsive Tags and Chips
https://blog.matrixpost.net/blazor-server-basics-part-ix-responsive-tags-and-chips/Blazor Server – Basics Part 10 – MS SQL Server Access and Data Binding
https://blog.matrixpost.net/blazor-server-basics-part-10-ms-sql-server-access-and-data-binding/Blazor Server – Basics Part 11 – Create a Native Blazor UI Toggle Switch Component
https://blog.matrixpost.net/blazor-server-basics-part-11-native-blazor-toggle-switch-by-using-the-eventcallback-class-and-css/Blazor Server – Basics Part 12 – Create a Native Blazor UI Toggle Button Component
https://blog.matrixpost.net/blazor-server-basics-part-12-create-a-native-blazor-ui-toggle-button-component/
Links
ASP.NET Core Blazor JavaScript interoperability (JS interop)
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-7.0Call JavaScript functions from .NET methods in ASP.NET Core Blazor
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-7.0Call .NET methods from JavaScript functions in ASP.NET Core Blazor
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-7.0ASP.NET Core Razor component lifecycle
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0