TLDR: Does JavaScript interop improve Blazor or complicate it? This blog evaluates the pros and cons. It discusses interop’s benefits like accessing browser APIs and existing JavaScript code integration. However, it also warns of performance issues, security risks, and added complexity. Developers integrating JavaScript into Blazor should weigh these factors.
Blazor is an open-source, single-page web application development framework. Unlike other frameworks, such as Angular, React and Vue, which depend on JavaScript libraries, Blazor allows you to write and run C# code in web browsers via WebAssembly.
The Blazor framework can, however, call JavaScript functions from .NET (C#) methods and vice versa. It handles the DOM (document object model) manipulation and browser API calls through a method called JavaScript interoperability (JS interop).
We can also use TypeScript in Blazor, as TypeScript is the superset of JavaScript. When we compile a Blazor project, the TypeScript file will be converted into a JavaScript file using the MSBuild properties.
In this blog post, we will see how to use JavaScript interop in Blazor, its pros, and its cons.
In a Blazor app, to call JavaScript functions from .NET, we need to inject the IJSRuntime abstraction and call the InvokeAsync method. The InvokeAsync method accepts the function name and number of arguments that the function requires.
ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
This method has three arguments: function name, CancellationToken (for notification of whether the operation is canceled or not), and the number of arguments that the function requires.
ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args)
To call static .NET methods from JavaScript functions in a Blazor app, use the DotNet.invokeMethod or DotNet.invokeMethodAsync method.
Refer to the following code example.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});
The previous method has three arguments:
Let’s see the advantages of using JavaScript interop in your Blazor application.
Using the JavaScript interop, we can easily inject the required code on-demand anywhere in our Blazor app. You can see the injected JavaScript function after the DOM is loaded in the Blazor app (both WebAssembly and Server apps).
Follow these steps to inject a script file in Blazor:
Refer to the following code example.
<body> <script src="_framework/blazor.{webassembly|server}.js" autostart="false"></script> <script> Blazor.start().then(function () { var customScript = document.createElement('script'); customScript.setAttribute('src', 'scripts.js'); document.head.appendChild(customScript); }); </script> </body>
Blazor allows JavaScript isolation in standard JavaScript modules. This JavaScript isolation feature provides the following benefits:
One of the major advantages of using JavaScript interop is that we can integrate any of the JavaScript frameworks into the Blazor app and achieve its functionalities.
In the following code, we are going to call the Syncfusion JS 2 JavaScript library animation functions in our Blazor server app.
[_Host.cshtml]
<head> <link href="JSinterop.styles.css" rel="stylesheet" /> @*Syncfusion library CDN script and CSS reference*@ <link href="https://cdn.syncfusion.com/ej2/ej2-base/styles/material.css" rel="stylesheet"> <script src="https://cdn.syncfusion.com/ej2/dist/ej2.min.js" type="text/javascript"></script> <script> window.initializeAnimation = () => { //initialize the Animation code. var animation = new ej.base.Animation({ duration: 5000 }); animation.animate('#element1', { name: 'FadeOut' }); animation.animate('#element2', { name: 'ZoomOut' }); } </script> </head>
[Index.razor]
@page "/" @inject IJSRuntime JsRuntime; <style> #element1, #element2 { background: #333333; border: 1px solid #cecece; box-sizing: border-box; float: left; height: 100px; width: 100px; } </style> <h1>Hello, world!</h1> <div id="element1"></div> <div id="element2"></div> @code{ bool firstRender = true; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // call the JS function. await JsRuntime.InvokeVoidAsync( "initializeAnimation"); } } }
In a web platform, while caching static resources (the JavaScript file on the client-side browser), a static file will be loaded from the cache by default. This cache file will reduce the number of requests to the server, so it enhances the loading speed of the pages.
You can further improve the performance of the browser cache using the catche-control with the value of no-catch or by setting the max-age value.
By default, the JavaScript interop calls asynchronous methods, so it is compatible with both Blazor server and WebAssembly apps.
Note: Blazor server’s JS interop calls should be asynchronous, as they’re sent over a network connection.
Even though the JavaScript interop in Blazor has so many advantages, there are some limitations to it.
The DOM is used for display purposes alone in browsers. Modifying the DOM using JavaScript code is not suggested, anyway, as JavaScript restricts updating a Blazor element’s tracked changes.
Consider a situation where an element is rendered by Blazor code and modified externally using JavaScript directly or via JavaScript interop. Then, the DOM structure may not match Blazor’s internal representation. This may lead to undefined behaviors.
The JavaScript interop call may fail due to a network issue and low bandwidth network in a server app. By default, it takes a one-minute timeout for each JavaScript call. Luckily, we have an option to increase the timeout value in the server app. But this may also lead to performance deterioration.
Refer to the following code.
[Program.CS]
builder.Services.AddServerSideBlazor( options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
We can override the global timeout set using the JSInteropDefaultCallTimeout method.
[C#]
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });
In a Blazor server app, JavaScript interop calls are limited in size while maximizing the incoming SignalR message permitted for hub methods.
If we pass a huge message (more than 32 KB) in a JS call, then it will throw an error.
However, we can increase the size of the SignalR message in the Program.CS file like in the following code.
[Program.CS]
builder.Services.AddServerSideBlazor() .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
Increasing the message size will also increase the risks for the user. Additionally, reading a huge amount of content into memory as strings or byte arrays can also result in poor allocation of memory with the garbage collector. Further, it results in additional performance penalties.
A Blazor app will experience poor performance when we serialize a huge amount of .NET objects and sent them to the JS interop call. For example, serializing huge .NET objects rapidly in the resize and mouse wheel events.
Calling the JS interop frequently may lead to performance lag. The synchronous calls that don’t perform JSON serialization of arguments or return values, the memory management, and translations between .NET and JavaScript also result in poor performance.
Thanks for reading! In this blog, we have seen JavaScript interop concepts used in the Blazor framework along with their pros and cons. With this JS interop, we can easily call JavaScript functions from .NET (C#) methods and vice versa.
Syncfusion’s Blazor component suite offers over 70 UI components that work with both server-side and client-side (WebAssembly) hosting models seamlessly. Use them to build marvelous applications!
If you have questions, you can contact us through our support forum, support portal, or feedback portal. As always, we are happy to assist you!