14 KiB
title | author | description | monikerRange | ms.author | ms.custom | ms.date | uid |
---|---|---|---|---|---|---|---|
ASP.NET Core Blazor JavaScript interop | guardrex | Learn how to invoke JavaScript functions from .NET and .NET methods from JavaScript in Blazor apps. | >= aspnetcore-3.0 | riande | mvc | 09/23/2019 | blazor/javascript-interop |
ASP.NET Core Blazor JavaScript interop
By Javier Calvarro Nelson, Daniel Roth, and Luke Latham
A Blazor app can invoke JavaScript functions from .NET and .NET methods from JavaScript code.
View or download sample code (how to download)
Invoke JavaScript functions from .NET methods
There are times when .NET code is required to call a JavaScript function. For example, a JavaScript call can expose browser capabilities or functionality from a JavaScript library to the app.
To call into JavaScript from .NET, use the IJSRuntime
abstraction. The InvokeAsync<T>
method takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments. The function identifier is relative to the global scope (window
). If you wish to call window.someScope.someFunction
, the identifier is someScope.someFunction
. There's no need to register the function before it's called. The return type T
must also be JSON serializable.
For Blazor Server apps:
- Multiple user requests are processed by the Blazor Server app. Don't call
JSRuntime.Current
in a component to invoke JavaScript functions. - Inject the
IJSRuntime
abstraction and use the injected object to issue JavaScript interop calls. - While a Blazor app is prerendering, calling into JavaScript isn't possible because a connection with the browser hasn't been established. For more information, see the Detect when a Blazor app is prerendering section.
The following example is based on TextDecoder, an experimental JavaScript-based decoder. The example demonstrates how to invoke a JavaScript function from a C# method. The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.
Inside the <head>
element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a function that uses TextDecoder
to decode a passed array:
JavaScript code, such as the code shown in the preceding example, can also be loaded from a JavaScript file (.js) with a reference to the script file:
<script src="exampleJsInterop.js"></script>
The following component:
- Invokes the
ConvertArray
JavaScript function usingJsRuntime
when a component button (Convert Array) is selected. - After the JavaScript function is called, the passed array is converted into a string. The string is returned to the component for display.
To use the IJSRuntime
abstraction, adopt any of the following approaches:
-
Inject the
IJSRuntime
abstraction into the Razor component (.razor): -
Inject the
IJSRuntime
abstraction into a class (.cs): -
For dynamic content generation with BuildRenderTree, use the
[Inject]
attribute:[Inject] IJSRuntime JSRuntime { get; set; }
In the client-side sample app that accompanies this topic, two JavaScript functions are available to the app that interact with the DOM to receive user input and display a welcome message:
showPrompt
– Produces a prompt to accept user input (the user's name) and returns the name to the caller.displayWelcome
– Assigns a welcome message from the caller to a DOM object with anid
ofwelcome
.
wwwroot/exampleJsInterop.js:
Place the <script>
tag that references the JavaScript file in the wwwroot/index.html file (Blazor WebAssembly) or Pages/_Host.cshtml file (Blazor Server).
wwwroot/index.html (Blazor WebAssembly):
Pages/_Host.cshtml (Blazor Server):
Don't place a <script>
tag in a component file because the <script>
tag can't be updated dynamically.
.NET methods interop with the JavaScript functions in the exampleJsInterop.js file by calling IJSRuntime.InvokeAsync<T>
.
The IJSRuntime
abstraction is asynchronous to allow for Blazor Server scenarios. If the app is a Blazor WebAssembly app and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime
and call Invoke<T>
instead. We recommend that most JavaScript interop libraries use the async APIs to ensure that the libraries are available in all scenarios.
The sample app includes a component to demonstrate JavaScript interop. The component:
- Receives user input via a JavaScript prompt.
- Returns the text to the component for processing.
- Calls a second JavaScript function that interacts with the DOM to display a welcome message.
Pages/JSInterop.razor:
- When
TriggerJsPrompt
is executed by selecting the component's Trigger JavaScript Prompt button, the JavaScriptshowPrompt
function provided in the wwwroot/exampleJsInterop.js file is called. - The
showPrompt
function accepts user input (the user's name), which is HTML-encoded and returned to the component. The component stores the user's name in a local variable,name
. - The string stored in
name
is incorporated into a welcome message, which is passed to a JavaScript function,displayWelcome
, which renders the welcome message into a heading tag.
Call a void JavaScript function
JavaScript functions that return void(0)/void 0 or undefined are called with IJSRuntime.InvokeVoidAsync
.
Detect when a Blazor app is prerendering
Capture references to elements
Some JavaScript interop scenarios require references to HTML elements. For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus
or play
.
Capture references to HTML elements in a component using the following approach:
- Add an
@ref
attribute to the HTML element. - Define a field of type
ElementReference
whose name matches the value of the@ref
attribute.
The following example shows capturing a reference to the username
<input>
element:
<input @ref="username" ... />
@code {
ElementReference username;
}
[!NOTE] Do not use captured element references as a way of populating the DOM. Doing so may interfere with the declarative rendering model.
As far as .NET code is concerned, an ElementReference
is an opaque handle. The only thing you can do with ElementReference
is pass it through to JavaScript code via JavaScript interop. When you do so, the JavaScript-side code receives an HTMLElement
instance, which it can use with normal DOM APIs.
For example, the following code defines a .NET extension method that enables setting the focus on an element:
exampleJsInterop.js:
window.exampleJsFunctions = {
focusElement : function (element) {
element.focus();
}
}
Use IJSRuntime.InvokeAsync<T>
and call exampleJsFunctions.focusElement
with an ElementReference
to focus an element:
To use an extension method to focus an element, create a static extension method that receives the IJSRuntime
instance:
public static Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
return jsRuntime.InvokeAsync<object>(
"exampleJsFunctions.focusElement", elementRef);
}
The method is called directly on the object. The following example assumes that the static Focus
method is available from the JsInteropClasses
namespace:
[!IMPORTANT] The
username
variable is only populated after the component is rendered. If an unpopulatedElementReference
is passed to JavaScript code, the JavaScript code receives a value ofnull
. To manipulate element references after the component has finished rendering (to set the initial focus on an element) use theOnAfterRenderAsync
orOnAfterRender
component lifecycle methods.
Invoke .NET methods from JavaScript functions
Static .NET method call
To invoke a static .NET method from JavaScript, use the DotNet.invokeMethod
or DotNet.invokeMethodAsync
functions. Pass in the identifier of the static method you wish to call, the name of the assembly containing the function, and any arguments. The asynchronous version is preferred to support Blazor Server scenarios. To invoke a .NET method from JavaScript, the .NET method must be public, static, and have the [JSInvokable]
attribute. By default, the method identifier is the method name, but you can specify a different identifier using the JSInvokableAttribute
constructor. Calling open generic methods isn't currently supported.
The sample app includes a C# method to return an array of int
s. The JSInvokable
attribute is applied to the method.
Pages/JsInterop.razor:
JavaScript served to the client invokes the C# .NET method.
wwwroot/exampleJsInterop.js:
When the Trigger .NET static method ReturnArrayAsync button is selected, examine the console output in the browser's web developer tools.
The console output is:
Array(4) [ 1, 2, 3, 4 ]
The fourth array value is pushed to the array (data.push(4);
) returned by ReturnArrayAsync
.
Instance method call
You can also call .NET instance methods from JavaScript. To invoke a .NET instance method from JavaScript:
- Pass the .NET instance to JavaScript by wrapping it in a
DotNetObjectReference
instance. The .NET instance is passed by reference to JavaScript. - Invoke .NET instance methods on the instance using the
invokeMethod
orinvokeMethodAsync
functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.
[!NOTE] The sample app logs messages to the client-side console. For the following examples demonstrated by the sample app, examine the browser's console output in the browser's developer tools.
When the Trigger .NET instance method HelloHelper.SayHello button is selected, ExampleJsInterop.CallHelloHelperSayHello
is called and passes a name, Blazor
, to the method.
Pages/JsInterop.razor:
CallHelloHelperSayHello
invokes the JavaScript function sayHello
with a new instance of HelloHelper
.
JsInteropClasses/ExampleJsInterop.cs:
wwwroot/exampleJsInterop.js:
The name is passed to HelloHelper
's constructor, which sets the HelloHelper.Name
property. When the JavaScript function sayHello
is executed, HelloHelper.SayHello
returns the Hello, {Name}!
message, which is written to the console by the JavaScript function.
JsInteropClasses/HelloHelper.cs:
Console output in the browser's web developer tools:
Hello, Blazor!
Share interop code in a class library
JavaScript interop code can be included in a class library, which allows you to share the code in a NuGet package.
The class library handles embedding JavaScript resources in the built assembly. The JavaScript files are placed in the wwwroot folder. The tooling takes care of embedding the resources when the library is built.
The built NuGet package is referenced in the app's project file the same way that any NuGet package is referenced. After the package is restored, app code can call into JavaScript as if it were C#.
For more information, see xref:blazor/class-libraries.
Harden JS interop calls
JS interop may fail due to networking errors and should be treated as unreliable. By default, a Blazor Server app times out JS interop calls on the server after one minute. If an app can tolerate a more aggressive timeout, such as 10 seconds, set the timeout using one of the following approaches:
-
Globally in
Startup.ConfigureServices
, specify the timeout:services.AddServerSideBlazor( options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
-
Per-invocation in component code, a single call can specify the timeout:
var result = await JSRuntime.InvokeAsync<string>("MyJSOperation", TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });
For more information on resource exhaustion, see xref:security/blazor/server.