title | author | description | monikerRange | ms.author | ms.custom | ms.date | uid |
---|---|---|---|---|---|---|---|
Call JavaScript functions from .NET methods in ASP.NET Core Blazor |
guardrex |
Learn how to invoke JavaScript functions from .NET methods in Blazor apps. |
>= aspnetcore-3.1 |
riande |
mvc |
10/27/2023 |
blazor/js-interop/call-javascript-from-dotnet |
This article explains how to invoke JavaScript (JS) functions from .NET.
For information on how to call .NET methods from JS, see xref:blazor/js-interop/call-dotnet-from-javascript.
xref:Microsoft.JSInterop.IJSRuntime is registered by the Blazor framework. To call into JS from .NET, inject the xref:Microsoft.JSInterop.IJSRuntime abstraction and call one of the following methods:
- xref:Microsoft.JSInterop.IJSRuntime.InvokeAsync%2A?displayProperty=nameWithType
- xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeAsync%2A?displayProperty=nameWithType
- xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync%2A?displayProperty=nameWithType
For the preceding .NET methods that invoke JS functions:
- The function identifier (
String
) is relative to the global scope (window
). To callwindow.someScope.someFunction
, the identifier issomeScope.someFunction
. There's no need to register the function before it's called. - Pass any number of JSON-serializable arguments in
Object[]
to a JS function. - The cancellation token (
CancellationToken
) propagates a notification that operations should be canceled. TimeSpan
represents a time limit for a JS operation.- The
TValue
return type must also be JSON serializable.TValue
should match the .NET type that best maps to the JSON type returned. - A JS
Promise
is returned forInvokeAsync
methods.InvokeAsync
unwraps thePromise
and returns the value awaited by thePromise
.
For Blazor apps with prerendering enabled, which is the default for server-side apps, calling into JS isn't possible during prerendering. For more information, see the Prerendering section.
The following example is based on TextDecoder
, a JS-based decoder. The example demonstrates how to invoke a JS function from a C# method that offloads a requirement from developer code to an existing JS API. The JS function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.
<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
console.log(decodedArray);
return decodedArray;
};
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
The following CallJsExample1
component:
- Invokes the
convertArray
JS function with xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeAsync%2A when selecting a button (Convert Array
). - After the JS function is called, the passed array is converted into a string. The string is returned to the component for display (
text
).
CallJsExample1.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-1"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample1.razor" highlight="2,34":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample1.razor" highlight="2,34":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample1.razor" highlight="2,34":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample1.razor" highlight="2,34-35":::
:::moniker-end
This section applies to server-side components.
Some browser JavaScript (JS) APIs can only be executed in the context of a user gesture, such as using the Fullscreen API
(MDN documentation). These APIs can't be called through the JS interop mechanism in server-side components because UI event handling is performed asynchronously and generally no longer in the context of the user gesture. The app must handle the UI event completely in JavaScript, so use onclick
instead of Blazor's @onclick
directive attribute.
Use xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync%2A when:
- .NET isn't required to read the result of a JavaScript (JS) call.
- JS functions return void(0)/void 0 or undefined.
Provide a displayTickerAlert1
JS function. The function is called with xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync%2A and doesn't return a value:
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
TickerChanged
calls the handleTickerChanged1
method in the following CallJsExample2
component.
CallJsExample2.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-2"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample2.razor" highlight="2,25":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample2.razor" highlight="2,25":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample2.razor" highlight="2,25":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample2.razor" highlight="2,25":::
:::moniker-end
JsInteropClasses1.cs
:
:::moniker range=">= aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/JsInteropClasses1.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/JsInteropClasses1.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/JsInteropClasses1.cs":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/JsInteropClasses1.cs":::
:::moniker-end
TickerChanged
calls the handleTickerChanged1
method in the following CallJsExample3
component.
CallJsExample3.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-3"
@rendermode InteractiveServer
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample3.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample3.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample3.razor":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample3.razor":::
:::moniker-end
Use xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeAsync%2A when .NET should read the result of a JavaScript (JS) call.
Provide a displayTickerAlert2
JS function. The following example returns a string for display by the caller:
<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
TickerChanged
calls the handleTickerChanged2
method and displays the returned string in the following CallJsExample4
component.
CallJsExample4.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-4"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample4.razor" highlight="2,31-34":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample4.razor" highlight="2,31-34":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample4.razor" highlight="2,31-34":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample4.razor" highlight="2,31-34":::
:::moniker-end
JsInteropClasses2.cs
:
:::moniker range=">= aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/JsInteropClasses2.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/JsInteropClasses2.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/JsInteropClasses2.cs":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/JsInteropClasses2.cs":::
:::moniker-end
TickerChanged
calls the handleTickerChanged2
method and displays the returned string in the following CallJsExample5
component.
CallJsExample5.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-5"
@rendermode InteractiveServer
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample5.razor" highlight="2-3,25,30,40-42,46":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample5.razor" highlight="2-3,25,30,40-42,46":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample5.razor" highlight="2-3,25,30,38-40,43":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample5.razor" highlight="2-3,25,30,38-40,43":::
:::moniker-end
For dynamic content generation with BuildRenderTree, use the [Inject]
attribute:
[Inject]
IJSRuntime JS { get; set; }
Load JavaScript (JS) code using any of approaches described by the JavaScript (JS) interoperability (interop) overview article:
:::moniker range=">= aspnetcore-6.0"
- Load a script in
<head>
markup (Not generally recommended) - Load a script in
<body>
markup - Load a script from an external JavaScript file (
.js
) collocated with a component - Load a script from an external JavaScript file (
.js
) - Inject a script before or after Blazor starts
:::moniker-end
:::moniker range="< aspnetcore-6.0"
- Load a script in
<head>
markup (Not generally recommended) - Load a script in
<body>
markup - Load a script from an external JavaScript file (
.js
) - Inject a script after Blazor starts
:::moniker-end
:::moniker range=">= aspnetcore-5.0"
For information on isolating scripts in JS modules, see the JavaScript isolation in JavaScript modules section.
:::moniker-end
Warning
Don't place a <script>
tag in a component file (.razor
) because the <script>
tag can't be updated dynamically.
:::moniker range=">= aspnetcore-5.0"
Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification). JavaScript module loading works the same way in Blazor as it does for other types of web apps, and you're free to customize how modules are defined in your app. For a guide on how to use JavaScript modules, see MDN Web Docs: JavaScript modules.
JS isolation provides the following benefits:
- Imported JS no longer pollutes the global namespace.
- Consumers of a library and components aren't required to import the related JS.
Dynamic import with the import()
operator is supported with ASP.NET Core and Blazor:
if ({CONDITION}) import("/additionalModule.js");
In the preceding example, the {CONDITION}
placeholder represents a conditional check to determine if the module should be loaded.
For browser compatibility, see Can I use: JavaScript modules: dynamic import.
For example, the following JS module exports a JS function for showing a browser window prompt. Place the following JS code in an external JS file.
wwwroot/scripts.js
:
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}
Add the preceding JS module to an app or class library as a static web asset in the wwwroot
folder and then import the module into the .NET code by calling xref:Microsoft.JSInterop.IJSRuntime.InvokeAsync%2A on the xref:Microsoft.JSInterop.IJSRuntime instance.
xref:Microsoft.JSInterop.IJSRuntime imports the module as an xref:Microsoft.JSInterop.IJSObjectReference, which represents a reference to a JS object from .NET code. Use the xref:Microsoft.JSInterop.IJSObjectReference to invoke exported JS functions from the module.
CallJsExample6.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-6"
@rendermode InteractiveServer
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample6.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample6.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample6.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0"
In the preceding example:
- By convention, the
import
identifier is a special identifier used specifically for importing a JS module. - Specify the module's external JS file using its stable static web asset path:
./{SCRIPT PATH AND FILE NAME (.js)}
, where:- The path segment for the current directory (
./
) is required in order to create the correct static asset path to the JS file. - The
{SCRIPT PATH AND FILE NAME (.js)}
placeholder is the path and file name underwwwroot
.
- The path segment for the current directory (
- Disposes the xref:Microsoft.JSInterop.IJSObjectReference for garbage collection in xref:System.IAsyncDisposable.DisposeAsync%2A?displayProperty=nameWithType.
Dynamically importing a module requires a network request, so it can only be achieved asynchronously by calling xref:Microsoft.JSInterop.IJSRuntime.InvokeAsync%2A.
IJSInProcessObjectReference
represents a reference to a JS object whose functions can be invoked synchronously in client-side components. For more information, see the Synchronous JS interop in client-side components section.
Note
When the external JS file is supplied by a Razor class library, specify the module's JS file using its stable static web asset path: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}
:
- The path segment for the current directory (
./
) is required in order to create the correct static asset path to the JS file. - The
{PACKAGE ID}
placeholder is the library's package ID. The package ID defaults to the project's assembly name if<PackageId>
isn't specified in the project file. In the following example, the library's assembly name isComponentLibrary
and the library's project file doesn't specify<PackageId>
. - The
{SCRIPT PATH AND FILE NAME (.js)}
placeholder is the path and file name underwwwroot
. In the following example, the external JS file (script.js
) is placed in the class library'swwwroot
folder.
var module = await js.InvokeAsync<IJSObjectReference>(
"import", "./_content/ComponentLibrary/scripts.js");
For more information, see xref:blazor/components/class-libraries.
Throughout the Blazor documentation, examples use the .js
file extension for module files, not the newer .mjs
file extension (RFC 9239). Our documentation continues to use the .js
file extension for the same reasons the Mozilla Foundation's documentation continues to use the .js
file extension. For more information, see Aside — .mjs versus .js (MDN documentation).
:::moniker-end
Some JavaScript (JS) 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 click
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 xref:Microsoft.AspNetCore.Components.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 {
private ElementReference username;
}
Warning
Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. This scenario is useful when a third-party API supplies content to the element. Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM.
In the following example, it's dangerous to mutate the contents of the unordered list (ul
) using MyList
via JS interop because Blazor interacts with the DOM to populate this element's list items (<li>
) from the Todos
object:
<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>
Using the MyList
element reference to merely read DOM content or trigger an event is supported.
If JS interop mutates the contents of element MyList
and Blazor attempts to apply diffs to the element, the diffs won't match the DOM. Modifying the contents of the list via JS interop with the MyList
element reference is not supported.
For more information, see xref:blazor/js-interop/index#interaction-with-the-document-object-model-dom.
An xref:Microsoft.AspNetCore.Components.ElementReference is passed through to JS code via JS interop. The JS code receives an HTMLElement
instance, which it can use with normal DOM APIs. For example, the following code defines a .NET extension method (TriggerClickEvent
) that enables sending a mouse click to an element.
The JS function clickElement
creates a click
event on the passed HTML element (element
):
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}
To call a JS function that doesn't return a value, use xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync%2A?displayProperty=nameWithType. The following code triggers a client-side click
event by calling the preceding JS function with the captured xref:Microsoft.AspNetCore.Components.ElementReference:
@inject IJSRuntime JS
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await JS.InvokeVoidAsync(
"interopFunctions.clickElement", exampleButton);
}
}
To use an extension method, create a static extension method that receives the xref:Microsoft.JSInterop.IJSRuntime instance:
public static async Task TriggerClickEvent(this ElementReference elementRef,
IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}
The clickElement
method is called directly on the object. The following example assumes that the TriggerClickEvent
method is available from the JsInteropClasses
namespace:
@inject IJSRuntime JS
@using JsInteropClasses
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await exampleButton.TriggerClickEvent(JS);
}
}
Important
The exampleButton
variable is only populated after the component is rendered. If an unpopulated xref:Microsoft.AspNetCore.Components.ElementReference is passed to JS code, the JS code receives a value of null
. To manipulate element references after the component has finished rendering, use the OnAfterRenderAsync
or OnAfterRender
component lifecycle methods.
When working with generic types and returning a value, use xref:System.Threading.Tasks.ValueTask%601:
public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef,
IJSRuntime js)
{
return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}
The {JAVASCRIPT FUNCTION}
placeholder is the JS function identifier.
GenericMethod
is called directly on the object with a type. The following example assumes that the GenericMethod
is available from the JsInteropClasses
namespace:
:::moniker range=">= aspnetcore-8.0"
@rendermode InteractiveServer
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
:::moniker-end
:::moniker range="< aspnetcore-6.0"
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
:::moniker-end
An xref:Microsoft.AspNetCore.Components.ElementReference can't be passed between components because:
- The instance is only guaranteed to exist after the component is rendered, which is during or after a component's xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A/xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A method executes.
- An xref:Microsoft.AspNetCore.Components.ElementReference is a
struct
, which can't be passed as a component parameter.
For a parent component to make an element reference available to other components, the parent component can:
- Allow child components to register callbacks.
- Invoke the registered callbacks during the xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A event with the passed element reference. Indirectly, this approach allows child components to interact with the parent's element reference.
<style>
.red { color: red }
</style>
<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
CallJsExample7.razor
(parent component):
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-7"
@rendermode InteractiveServer
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor" highlight="5,9":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor" highlight="5,9":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor" highlight="5,9":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/CallJsExample7.razor" highlight="5,9":::
:::moniker-end
CallJsExample7.razor.cs
:
:::moniker range=">= aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/CallJsExample7.razor.cs":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/CallJsExample7.razor.cs":::
:::moniker-end
In the preceding example, the namespace of the app is BlazorSample
. If testing the code locally, update the namespace.
SurveyPrompt.razor
(child component):
:::moniker range=">= aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor":::
:::moniker-end
SurveyPrompt.razor.cs
:
:::moniker range=">= aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor.cs":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/SurveyPrompt.razor.cs":::
:::moniker-end
In the preceding example, the namespace of the app is BlazorSample
with shared components in the Shared
folder. If testing the code locally, update the namespace.
This section only applies to Interactive Server components, but client-side components may also set JS interop timeouts if conditions warrant it.
In server-side apps with server interactivity, JavaScript (JS) interop may fail due to networking errors and should be treated as unreliable. By default, Blazor apps use a one minute timeout for JS interop calls. If an app can tolerate a more aggressive timeout, set the timeout using one of the following approaches.
:::moniker range=">= aspnetcore-6.0"
Set a global timeout in the Program.cs
with xref:Microsoft.AspNetCore.Components.Server.CircuitOptions.JSInteropDefaultCallTimeout?displayProperty=nameWithType:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"
builder.Services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
:::moniker-end
:::moniker range="< aspnetcore-6.0"
Set a global timeout in the Startup.ConfigureServices
method of Startup.cs
with xref:Microsoft.AspNetCore.Components.Server.CircuitOptions.JSInteropDefaultCallTimeout?displayProperty=nameWithType:
services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
:::moniker-end
The {TIMEOUT}
placeholder is a xref:System.TimeSpan (for example, TimeSpan.FromSeconds(80)
).
Set a per-invocation timeout in component code. The specified timeout overrides the global timeout set by xref:Microsoft.AspNetCore.Components.Server.CircuitOptions.JSInteropDefaultCallTimeout:
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });
In the preceding example:
- The
{TIMEOUT}
placeholder is a xref:System.TimeSpan (for example,TimeSpan.FromSeconds(80)
). - The
{ID}
placeholder is the identifier for the function to invoke. For example, the valuesomeScope.someFunction
invokes the functionwindow.someScope.someFunction
.
Although a common cause of JS interop failures are network failures with server-side components, per-invocation timeouts can be set for JS interop calls for client-side components. Although no SignalR circuit exists for a client-side component, JS interop calls might fail for other reasons that apply.
For more information on resource exhaustion, see xref:blazor/security/server/threat-mitigation.
Objects that contain circular references can't be serialized on the client for either:
- .NET method calls.
- JavaScript method calls from C# when the return type has circular references.
:::moniker range=">= aspnetcore-6.0"
Sometimes you may wish to use JavaScript (JS) libraries that produce visible user interface elements within the browser DOM. At first glance, this might seem difficult because Blazor's diffing system relies on having control over the tree of DOM elements and runs into errors if some external code mutates the DOM tree and invalidates its mechanism for applying diffs. This isn't a Blazor-specific limitation. The same challenge occurs with any diff-based UI framework.
Fortunately, it's straightforward to embed externally-generated UI within a Razor component UI reliably. The recommended technique is to have the component's code (.razor
file) produce an empty element. As far as Blazor's diffing system is concerned, the element is always empty, so the renderer does not recurse into the element and instead leaves its contents alone. This makes it safe to populate the element with arbitrary externally-managed content.
The following example demonstrates the concept. Within the if
statement when firstRender
is true
, interact with unmanagedElement
outside of Blazor using JS interop. For example, call an external JS library to populate the element. Blazor leaves the element's contents alone until this component is removed. When the component is removed, the component's entire DOM subtree is also removed.
<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>
<div @ref="unmanagedElement"></div>
@code {
private ElementReference unmanagedElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
...
}
}
}
Consider the following example that renders an interactive map using open-source Mapbox APIs.
The following JS module is placed into the app or made available from a Razor class library.
Note
To create the Mapbox map, obtain an access token from Mapbox Sign in and provide it where the {ACCESS TOKEN}
appears in the following code.
wwwroot/mapComponent.js
:
import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';
mapboxgl.accessToken = '{ACCESS TOKEN}';
export function addMapToElement(element) {
return new mapboxgl.Map({
container: element,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
}
export function setMapCenter(map, latitude, longitude) {
map.setCenter([longitude, latitude]);
}
To produce correct styling, add the following stylesheet tag to the host HTML page.
Add the following <link>
element to the <head>
element markup (location of <head>
content):
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
CallJsExample8.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-8"
@rendermode InteractiveServer
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude).AsTask();
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample8.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample8.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0"
The preceding example produces an interactive map UI. The user:
- Can drag to scroll or zoom.
- Select buttons to jump to predefined locations.
In the preceding example:
- The
<div>
with@ref="mapElement"
is left empty as far as Blazor is concerned. Themapbox-gl.js
script can safely populate the element and modify its contents. Use this technique with any JS library that renders UI. You can embed components from a third-party JS SPA framework inside Razor components, as long as they don't try to reach out and modify other parts of the page. It is not safe for external JS code to modify elements that Blazor does not regard as empty. - When using this approach, bear in mind the rules about how Blazor retains or destroys DOM elements. The component safely handles button click events and updates the existing map instance because DOM elements are retained where possible by default. If you were rendering a list of map elements from inside a
@foreach
loop, you want to use@key
to ensure the preservation of component instances. Otherwise, changes in the list data could cause component instances to retain the state of previous instances in an undesirable manner. For more information, see how to use the@key
directive attribute to preserve the relationship among elements, components, and model objects. - The example encapsulates JS logic and dependencies within an ES6 module and loads the module dynamically using the
import
identifier. For more information, see JavaScript isolation in JavaScript modules.
:::moniker-end
:::moniker range=">= aspnetcore-6.0"
Blazor supports optimized byte array JavaScript (JS) interop that avoids encoding/decoding byte arrays into Base64. The following example uses JS interop to pass a byte array to JavaScript.
Provide a receiveByteArray
JS function. The function is called with xref:Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync%2A and doesn't return a value:
<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
CallJsExample9.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-9"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Call JS Example 9</h1>
<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>
<p>
@result
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>
@code {
private string? result;
private async Task SendByteArray()
{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };
result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
}
}
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"
@page "/call-js-example-9"
@inject IJSRuntime JS
<h1>Call JS Example 9</h1>
<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>
<p>
@result
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>
@code {
private string? result;
private async Task SendByteArray()
{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };
result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
}
}
For information on using a byte array when calling .NET from JavaScript, see xref:blazor/js-interop/call-dotnet-from-javascript#byte-array-support.
:::moniker-end
:::moniker range=">= aspnetcore-6.0"
Blazor supports streaming data directly from .NET to JavaScript. Streams are created using a xref:Microsoft.JSInterop.DotNetStreamReference.
xref:Microsoft.JSInterop.DotNetStreamReference represents a .NET stream and uses the following parameters:
stream
: The stream sent to JavaScript.leaveOpen
: Determines if the stream is left open after transmission. If a value isn't provided,leaveOpen
defaults tofalse
.
In JavaScript, use an array buffer or a readable stream to receive the data:
-
Using an
ArrayBuffer
:async function streamToJavaScript(streamRef) { const data = await streamRef.arrayBuffer(); }
-
Using a
ReadableStream
:async function streamToJavaScript(streamRef) { const stream = await streamRef.stream(); }
In C# code:
using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);
In the preceding example:
- The
{STREAM}
placeholder represents the xref:System.IO.Stream sent to JavaScript. JS
is an injected xref:Microsoft.JSInterop.IJSRuntime instance.
xref:blazor/js-interop/call-dotnet-from-javascript#stream-from-javascript-to-net covers the reverse operation, streaming from JavaScript to .NET.
xref:blazor/file-downloads covers how to download a file in Blazor.
:::moniker-end
To catch JS exceptions, wrap the JS interop in a try
-catch
block and catch a xref:Microsoft.JSInterop.JSException.
In the following example, the nonFunction
JS function doesn't exist. When the function isn't found, the xref:Microsoft.JSInterop.JSException is trapped with a xref:System.Exception.Message that indicates the following error:
Could not find 'nonFunction' ('nonFunction' was undefined).
CallJsExample11.razor
:
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-11"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample11.razor" highlight="28":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample11.razor" highlight="28":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample11.razor" highlight="28":::
:::moniker-end
:::moniker range="< aspnetcore-5.0"
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample11.razor" highlight="28":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0"
Use a JS AbortController with a xref:System.Threading.CancellationTokenSource in the component to abort a long-running JavaScript function from C# code.
The following JS Helpers
class contains a simulated long-running function, longRunningFn
, to count continuously until the AbortController.signal
indicates that AbortController.abort
has been called. The sleep
function is for demonstration purposes to simulate slow execution of the long-running function and wouldn't be present in production code. When a component calls stopFn
, the longRunningFn
is signalled to abort via the while
loop conditional check on AbortSignal.aborted
.
<script>
class Helpers {
static #controller = new AbortController();
static async #sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
static async longRunningFn() {
var i = 0;
while (!this.#controller.signal.aborted) {
i++;
console.log(`longRunningFn: ${i}`);
await this.#sleep(1000);
}
}
static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}
window.Helpers = Helpers;
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
The following CallJsExample12
component:
- Invokes the JS function
longRunningFn
when theStart Task
button is selected. A xref:System.Threading.CancellationTokenSource is used to manage the execution of the long-running function. xref:System.Threading.CancellationToken.Register%2A?displayProperty=nameWithType sets a JS interop call delegate to execute the JS functionstopFn
when the xref:System.Threading.CancellationTokenSource.Token?displayProperty=nameWithType is cancelled. - When the
Cancel Task
button is selected, the xref:System.Threading.CancellationTokenSource.Token?displayProperty=nameWithType is cancelled with a call to xref:System.Threading.CancellationTokenSource.Cancel%2A. - The xref:System.Threading.CancellationTokenSource is disposed in the
Dispose
method.
CallJsExample12.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
@page "/call-js-example-12"
@rendermode InteractiveServer
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"
@page "/call-js-example-12"
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
:::moniker-end
:::moniker range=">= aspnetcore-6.0"
A browser's developer tools console indicates the execution of the long-running JS function after the Start Task
button is selected and when the function is aborted after the Cancel Task
button is selected:
longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!
:::moniker-end
:::moniker range=">= aspnetcore-7.0"
This section applies to client-side components.
As an alternative to interacting with JavaScript (JS) in client-side components using Blazor's JS interop mechanism based on the xref:Microsoft.JSInterop.IJSRuntime interface, a JS [JSImport]
/[JSExport]
interop API is available to apps targeting .NET 7 or later.
For more information, see xref:blazor/js-interop/import-export-interop.
:::moniker-end
:::moniker range=">= aspnetcore-7.0"
This section applies to client-side components.
Unmarshalled interop using the xref:Microsoft.JSInterop.IJSUnmarshalledRuntime interface is obsolete and should be replaced with JavaScript [JSImport]
/[JSExport]
interop.
For more information, see xref:blazor/js-interop/import-export-interop.
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-7.0"
Blazor WebAssembly components may experience poor performance when .NET objects are serialized for JavaScript (JS) interop and either of the following are true:
- A high volume of .NET objects are rapidly serialized. For example, poor performance may result when JS interop calls are made on the basis of moving an input device, such as spinning a mouse wheel.
- Large .NET objects or many .NET objects must be serialized for JS interop. For example, poor performance may result when JS interop calls require serializing dozens of files.
xref:Microsoft.JSInterop.IJSUnmarshalledObjectReference represents a reference to an JS object whose functions can be invoked without the overhead of serializing .NET data.
In the following example:
- A struct containing a string and an integer is passed unserialized to JS.
- JS functions process the data and return either a boolean or string to the caller.
- A JS string isn't directly convertible into a .NET
string
object. TheunmarshalledFunctionReturnString
function callsBINDING.js_string_to_mono_string
to manage the conversion of a JS string.
Note
The following examples aren't typical use cases for this scenario because the struct passed to JS doesn't result in poor component performance. The example uses a small object merely to demonstrate the concepts for passing unserialized .NET data.
<script>
window.returnObjectReference = () => {
return {
unmarshalledFunctionReturnBoolean: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
year === 1968;
},
unmarshalledFunctionReturnString: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
}
};
}
</script>
Note
For general guidance on JS location and our recommendations for production apps, see xref:blazor/js-interop/index#javascript-location.
Warning
The js_string_to_mono_string
function name, behavior, and existence is subject to change in a future release of .NET. For example:
- The function is likely to be renamed.
- The function itself might be removed in favor of automatic conversion of strings by the framework.
CallJsExample10.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample10.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/call-js-from-dotnet/CallJsExample10.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-5.0 < aspnetcore-7.0"
If an xref:Microsoft.JSInterop.IJSUnmarshalledObjectReference instance isn't disposed in C# code, it can be disposed in JS. The following dispose
function disposes the object reference when called from JS:
window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
return {
dispose: function () {
DotNet.disposeJSObjectReference(this);
},
...
};
}
Array types can be converted from JS objects into .NET objects using js_typed_array_to_array
, but the JS array must be a typed array. Arrays from JS can be read in C# code as a .NET object array (object[]
).
Other data types, such as string arrays, can be converted but require creating a new Mono array object (mono_obj_array_new
) and setting its value (mono_obj_array_set
).
Warning
JS functions provided by the Blazor framework, such as js_typed_array_to_array
, mono_obj_array_new
, and mono_obj_array_set
, are subject to name changes, behavioral changes, or removal in future releases of .NET.
:::moniker-end
Examples throughout the JavaScript (JS) interop articles demonstrate typical object disposal patterns:
-
When calling JS from .NET, as described in this article, dispose any created xref:Microsoft.JSInterop.IJSObjectReference/xref:Microsoft.JSInterop.IJSInProcessObjectReference/
JSObjectReference
either from .NET or from JS to avoid leaking JS memory. -
When calling .NET from JS, as described in xref:blazor/js-interop/call-dotnet-from-javascript, dispose of a created xref:Microsoft.JSInterop.DotNetObjectReference either from .NET or from JS to avoid leaking .NET memory.
JS interop object references are implemented as a map keyed by an identifier on the side of the JS interop call that creates the reference. When object disposal is initiated from either the .NET or JS side, Blazor removes the entry from the map, and the object can be garbage collected as long as no other strong reference to the object is present.
At a minimum, always dispose objects created on the .NET side to avoid leaking .NET managed memory.
For more information, see xref:blazor/js-interop/index#dom-cleanup-tasks-during-component-disposal.
For more information, see xref:blazor/js-interop/index#javascript-interop-calls-without-a-circuit.
- xref:blazor/js-interop/call-dotnet-from-javascript
InteropComponent.razor
example (dotnet/AspNetCore GitHub repositorymain
branch): Themain
branch represents the product unit's current development for the next release of ASP.NET Core. To select the branch for a different release (for example,release/5.0
), use the Switch branches or tags dropdown list to select the branch.- Blazor samples GitHub repository (
dotnet/blazor-samples
) - xref:blazor/fundamentals/handle-errors#javascript-interop (JavaScript interop section)
- Threat mitigation: JavaScript functions invoked from .NET