Unlike other SPA technologies, Blazor Server uses web assembly on the client side. That does not mean Blazor Server can't or should not use JavaScript. JavaScript is a traditional web development language and has a great number of libraries. Blazor Server has an interface IJSRuntime
to access JavaScript. This guide shows how to interact with JavaScript in Blazor Server like calling a function, import JS only when needed and error handling and more to give you the idea how to work with JavaScript.
You can download the example code used in this topic on GitHub.
To get the most information out of this guide, you need to understand the basic things below.
There are 2 types of events in Blazor, which are vanilla HTML 5 event and Blazor event. For example, we have 2 <button>
tags as below.
<button onclick="alert('Hello world')">HTML5 event Hello world!</button> <button @onclick='async () => await JSRuntime.InvokeVoidAsync("alert", "Hello world")'>Blazor event Hello world!</button>
The former tag uses onclick
which is an HTML5 event and the latter tag uses @onclick
which is a Blazor event. Both tags are doing the same thing when clicked that is emitting a message to the user but in the declaration they are quite different. In this guide, we assume you know how HTML5 events work and we only cover Blazor events.
When you call a JavaScript function, you must know that if the calling function returns anything because C# is a strongly typed language whereas JavaScript is a weakly typed language.
A void function is a function that does not return anything. To call a void function, you need to call the InvokeVoidAsync
method of IJSRuntime
. In the previous example, you have seen how to call a void function.
await JSRuntime.InvokeVoidAsync("alert", "Hello world")
The first parameter is the calling path to the function, it is scoped to window
object. In this example, the calling path is not alert()
but window.alert()
. That means if you have a JavaScript object that you are not attached to window
, you will not able to access it. The second parameter is the list of parameters to pass to the calling function.
JavaScript is a weakly-typed language whereas C# is a strongly-typed language. For that reason you need to know the return type of the function before calling it.
To call a function with return value, you need to call the InvokeAsync<T>
method of IJSRuntime
. Here is a snippet from our SimpleJavaScript.razor
example.
<button @onclick="EncodeSpace">Encode space</button> <div>Encode result: @EncodeResult</div> @code { public string EncodeResult { get; set; } private async Task EncodeSpace() { EncodeResult = await JSRuntime.InvokeAsync<string>("encodeURI", " "); } }
In this example, you are calling window.encodeURI(" ")
function and you are expecting a string return by specifying the generic for the InvokeAsync
method JSRuntime.InvokeAsync<string>
.
Most of the time, problems can not be solved within one line JavaScript function and you have to create a method to be called again and again. For example, you may want to access features that Blazor Server does not currently support or perform complex JavaScript operations. In that case, you will need to declare complex functions. This section will walk you through how to create a simple calculator JavaScript library and use it in Blazor Server.
Calculator.js
and BlazorUtil.js
in wwwroot/js
folder. You have to create the js
folder yourself.export class Calculator { minus(leftHand, rightHand) { return leftHand - rightHand; } }
import { Calculator } from "./Calculator.js"; class BlazorUtil { calculator = new Calculator(); } window.BlazorUtil = new BlazorUtil();
The idea is to make your javascript files modular. You can have another library like Calculator.js
and BlazorUtil.js
will centralize all of them. You also need to attach BlazorUtil to window
by window.BlazorUtil = new BlazorUtil();
as IJSRuntime
only interact with window
.
BlazorUtil.js
in _Host.cshtml
.... <script src="_framework/blazor.server.js"></script> <script src="/js/BlazorUtil.js" type="module"></script>
minus(leftHand, rightHand)
.@inject IJSRuntime JSRuntime <form> <div> <label>Left Hand:<input type="number" @bind-value="LeftHand" /></label> </div> <div> <label>Right Hand:<input type="number" @bind-value="RightHand" /></label> </div> <div> <label>Result: @Result</label> </div> <button type="button" @onclick="Minus">Minus</button> </form> @code { public int LeftHand { get; set; } public int RightHand { get; set; } public int Result { get; set; } private async Task Minus() { Result = await JSRuntime.InvokeAsync<int>("BlazorUtil.calculator.minus", LeftHand, RightHand); } }
JavaScript is a dynamic language which leads to its functions usually having unpredictable results. For example, a function does not have a return type and it can return a number, a string, a date, or does not return anything at all. Sometimes, a function takes too long to return results which will make Blazor Server website need to be refreshed by default.
When calling a JavaScript function, you should always wrap it between a try-catch
block because a single error in the JavaScript side will force the user to reload.
The first unpredictable thing when calling a JavaScript function is you don't know extractly the return type of a function. We have set up a scenario to demonstrate how to handle this case.
JavaScript function:
returnValueSometimes(inputFactor) { switch (inputFactor) { case 1: return 100; case 2: return "Blazor School"; default: } };
Razor Component:
<form> <div> <label> Input Factor: <input type="number" @bind-value="InputFactor" /> </label> <label>Unpredictable Result: @UnpredictableResult</label> </div> <div> <button type="button" @onclick="ExecuteUnpredictableFunction">Get Unpredictable Result</button> </div> </form> @code { public int InputFactor { get; set; } = 1; public dynamic UnpredictableResult { get; set; } = 0; private async Task ExecuteUnpredictableFunction() { try { UnpredictableResult = await JSRuntime.InvokeAsync<dynamic>("BlazorUtil.unpredictable.returnValueSometimes", InputFactor) ?? throw new InvalidOperationException(); } catch { UnpredictableResult = "Error occured."; } } }
Make the return type is dynamic
and wrap the calling InvokeAsync
inside a try-catch
block. But then, you have to handle the dynamic
result in C# code.
Sometimes, JavaScript functions are taking too long to respond. By default, if a function is taking too long to respond, Blazor Server will cancel after 60 seconds by default. Sometimes, you need to extend or shorten that timeout. We have set up a scenario to demonstrate how to set a timeout for a JavaScript function.
JavaScript function:
async hang() { await sleep(3); console.info("Execution on the client side has done!"); return "Done"; } function sleep(seconds) { return new Promise(resolve => setTimeout(resolve, seconds * 1000)); }
Razor Component:
... <button type="button" @onclick="ExecuteUnpredictableFunction">Get Unpredictable Result</button> <button type="button" @onclick="ExecuteHangingFunction">Execute Hanging Function</button> @code { ... private async Task ExecuteHangingFunction() { CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(InputFactor)); CancellationToken token = source.Token; try { UnpredictableResult = await JSRuntime.InvokeAsync<string>("BlazorUtil.unpredictable.hang", cancellationToken: token) ?? throw new InvalidOperationException(); } catch { UnpredictableResult = "Time out"; } } }
You can set a timeout by creating a new CancellationTokenSource(delay))
. You can cancel the JavaScript method anytime you want. To cancel before the timeout, call source.Cancel();
method from the above example. Keep in mind that when you cancelling a JavaScript function in C#, you are telling C# to not to wait for the JS function but not telling the JS function to stop, the JS function still continues to execute.
Having everything imported when the users don't need it is a waste of resources and can slow the initial loading phrase down. Blazor Server supports importing on demand. Let's say we have this library CustomLibrary.js
:
export class CustomLibrary { helloWorld() { alert("Hello World from CustomLibrary.js"); }; }
Declare a new import(path, moduleName)
in BlazorUtil.js
:
async import(path, moduleName) { let module = await import(path); let instance = new module[moduleName]; let instanceName = `${moduleName[0].toLowerCase()}${moduleName.slice(1)}`; this[instanceName] = instance; }
Import the library on demand and call its functions in Razor Component:
@inject IJSRuntime JSRuntime <button type="button" @onclick="Import">Import Custom Library</button> <button type="button" @onclick="HelloWorld">Hello World!</button> @code { private async Task Import() { await JSRuntime.InvokeVoidAsync("BlazorUtil.import", "./CustomLibrary.js", "CustomLibrary"); } private async Task HelloWorld() { await JSRuntime.InvokeVoidAsync("BlazorUtil.customLibrary.helloWorld"); } }