Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
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).
Further JS interop guidance is provided in the following articles:
Note
JavaScript [JSImport]
/[JSExport]
interop API is available for client-side components in ASP.NET Core in .NET 7 or later.
For more information, see JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.
With compression, which is enabled by default, avoid creating secure (authenticated/authorized) interactive server-side components that render data from untrusted sources. Untrusted sources include route parameters, query strings, data from JS interop, and any other source of data that a third-party user can control (databases, external services). For more information, see ASP.NET Core Blazor SignalR guidance and Threat mitigation guidance for ASP.NET Core Blazor interactive server-side rendering.
The @microsoft/dotnet-js-interop
package (npmjs.com
) (Microsoft.JSInterop
NuGet package) provides abstractions and features for interop between .NET and JavaScript (JS) code. Reference source is available in the dotnet/aspnetcore
GitHub repository (/src/JSInterop
folder). For more information, see the GitHub repository's README.md
file.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
Additional resources for writing JS interop scripts in TypeScript:
Only mutate the DOM with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. 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.
This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.
In a few documentation examples, JS interop is used to mutate an element purely for demonstration purposes as part of an example. In those cases, a warning appears in the text.
For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.
A JavaScript class with a field of type function isn't supported by Blazor JS interop. Use Javascript functions in classes.
Unsupported: GreetingHelpers.sayHello
in the following class as a field of type function isn't discovered by Blazor's JS interop and can't be executed from C# code:
export class GreetingHelpers {
sayHello = function() {
...
}
}
Supported: GreetingHelpers.sayHello
in the following class as a function is supported:
export class GreetingHelpers {
sayHello() {
...
}
}
Arrow functions are also supported:
export class GreetingHelpers {
sayHello = () => {
...
}
}
A JavaScript function can be invoked directly from an inline event handler. In the following example, alertUser
is a JavaScript function called when the button is selected by the user:
<button onclick="alertUser">Click Me!</button>
However, the use of inline event handlers is a poor design choice for calling JavaScript functions:
We recommend avoiding inline event handlers in favor of approaches that assign handlers in JavaScript with addEventListener
, as the following example demonstrates:
AlertUser.razor.js
:
export function alertUser() {
alert('The button was selected!');
}
export function addHandlers() {
const btn = document.getElementById("btn");
btn.addEventListener("click", alertUser);
}
AlertUser.razor
:
@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Alert User</h1>
<p>
<button id="btn">Click Me!</button>
</p>
@code {
private IJSObjectReference? module;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./Components/Pages/AlertUser.razor.js");
await module.InvokeVoidAsync("addHandlers");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
In the preceding example, JSDisconnectedException is trapped during module disposal in case Blazor's SignalR circuit is lost. If the preceding code is used in a Blazor WebAssembly app, there's no SignalR connection to lose, so you can remove the try
-catch
block and leave the line that disposes the module (await module.DisposeAsync();
). For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).
For more information, see the following resources:
JS interop calls are asynchronous, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous to ensure that components are compatible across server-side and client-side rendering models. When adopting server-side rendering, JS interop calls must be asynchronous because they're sent over a network connection. For apps that exclusively adopt client-side rendering, synchronous JS interop calls are supported.
For more information, see the following articles:
For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.
Blazor uses System.Text.Json for serialization with the following requirements and default behaviors:
get
/set
accessors must be public, and fields are never serialized.JsonSerializerIsReflectionEnabledByDefault
to false
in the app's project file results in an error when serialization is attempted.JsonConverter API is available for custom serialization. Properties can be annotated with a [JsonConverter]
attribute to override default serialization for an existing data type.
For more information, see the following resources in the .NET documentation:
System.Text.Json
Blazor supports optimized byte array JS interop that avoids encoding/decoding byte arrays into Base64. The app can apply custom serialization and pass the resulting bytes. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.
Blazor supports unmarshalled JS interop when a high volume of .NET objects are rapidly serialized or when large .NET objects or many .NET objects must be serialized. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.
Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the MutationObserver
pattern in JavaScript (JS) on the client for the following reasons:
Dispose{Async}
.Dispose{Async}
.The MutationObserver
pattern allows you to run a function when an element is removed from the DOM.
In the following example, the DOMCleanup
component:
<div>
with an id
of cleanupDiv
. The <div>
element is removed from the DOM along with the rest of the component's DOM markup when the component is removed from the DOM.DOMCleanup
JS class from the DOMCleanup.razor.js
file and calls its createObserver
function to set up the MutationObserver
callback. These tasks are accomplished in the OnAfterRenderAsync
lifecycle method.DOMCleanup.razor
:
@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>DOM Cleanup Example</h1>
<div id="cleanupDiv"></div>
@code {
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Pages/DOMCleanup.razor.js");
await module.InvokeVoidAsync("DOMCleanup.createObserver");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
In the preceding example, JSDisconnectedException is trapped during module disposal in case Blazor's SignalR circuit is lost. If the preceding code is used in a Blazor WebAssembly app, there's no SignalR connection to lose, so you can remove the try
-catch
block and leave the line that disposes the module (await module.DisposeAsync();
). For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).
In the following example, the MutationObserver
callback is executed each time a DOM change occurs. Execute your cleanup code when the if
statement confirms that the target element (cleanupDiv
) was removed (if (targetRemoved) { ... }
). It's important to disconnect and delete the MutationObserver
to avoid a memory leak after your cleanup code executes.
DOMCleanup.razor.js
placed side-by-side with the preceding DOMCleanup
component:
export class DOMCleanup {
static observer;
static createObserver() {
const target = document.querySelector('#cleanupDiv');
this.observer = new MutationObserver(function (mutations) {
const targetRemoved = mutations.some(function (mutation) {
const nodes = Array.from(mutation.removedNodes);
return nodes.indexOf(target) !== -1;
});
if (targetRemoved) {
// Cleanup resources here
// ...
// Disconnect and delete MutationObserver
this.observer && this.observer.disconnect();
delete this.observer;
}
});
this.observer.observe(target.parentNode, { childList: true });
}
}
window.DOMCleanup = DOMCleanup;
The preceding approaches attach the MutationObserver
to target.parentNode
, which works until parentNode
itself is removed from the DOM. This is a common scenario, for example, when navigating to a new page, which causes the entire page component to be removed from the DOM. In such cases, any child components observing changes within the page aren't cleaned up properly.
Don't assume that observing document.body
, instead of target.parentNode
, is a better target. Observing document.body
has performance implications because callback logic is executed for all DOM updates, whether or not they have anything to do with your element. Use either of the following approaches:
MutationObserver
with it. Ideally, this ancestor is scoped to the changes that you want to observe, rather than document.body
.MutationObserver
, consider using a custom element and disconnectedCallback
. The event always fires when your custom element is disconnected, no matter where it resides in the DOM relative to the DOM change.This section only applies to server-side apps.
JavaScript (JS) interop calls can't be issued after Blazor's SignalR circuit is disconnected. Without a circuit during component disposal or at any other time that a circuit doesn't exist, the following method calls fail and log a message that the circuit is disconnected as a JSDisconnectedException:
Dispose
/DisposeAsync
calls on any IJSObjectReference.In order to avoid logging JSDisconnectedException or to log custom information, catch the exception in a try-catch
statement.
For the following component disposal example:
module
is an IJSObjectReference for a JS module.catch
statement at whatever log level you prefer. The following example doesn't log custom information because it assumes the developer doesn't care about when or where circuits are disconnected during component disposal.async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (module is not null)
{
await module.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
If you must clean up your own JS objects or execute other JS code on the client after a circuit is lost in a server-side Blazor app, use the MutationObserver
pattern in JS on the client. The MutationObserver
pattern allows you to run a function when an element is removed from the DOM.
For more information, see the following articles:
IDisposable
and IAsyncDisposable
section describes how to implement disposal patterns in Razor components.JavaScript (JS) files and other static assets aren't generally cached on clients during development in the Development
environment. During development, static asset requests include the Cache-Control
header with a value of no-cache
or max-age
with a value of zero (0
).
During production in the Production
environment, JS files are usually cached by clients.
To disable client-side caching in browsers, developers usually adopt one of the following approaches:
Cache-Control
header sent by a client.For more information, see:
This section only applies to interactive components in server-side apps. For client-side components, the framework doesn't impose a limit on the size of JavaScript (JS) interop inputs and outputs.
For interactive components in server-side apps, JS interop calls passing data from the client to the server are limited in size by the maximum incoming SignalR message size permitted for hub methods, which is enforced by HubOptions.MaximumReceiveMessageSize (default: 32 KB). JS to .NET SignalR messages larger than MaximumReceiveMessageSize throw an error. The framework doesn't impose a limit on the size of a SignalR message from the hub to a client. For more information on the size limit, error messages, and guidance on dealing with message size limits, see ASP.NET Core Blazor SignalR guidance.
If it's relevant for the app to know where code is running for JS interop calls, use OperatingSystem.IsBrowser to determine if the component is executing in the context of browser on WebAssembly.
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreTraining
Module
Build rich interactive components with Blazor web apps - Training
Learn how to interoperate Blazor apps with JavaScript code, use templated components, and handle component lifecycle events.
Documentation
JavaScript location in ASP.NET Core Blazor apps
Learn where to place and how to load JavaScript in Blazor apps.
Call .NET methods from JavaScript functions in ASP.NET Core Blazor
Learn how to invoke .NET methods from JavaScript functions in Blazor apps.
Call JavaScript functions from .NET methods in ASP.NET Core Blazor
Learn how to invoke JavaScript functions from .NET methods in Blazor apps.