This article explains how to invoke .NET methods from JavaScript (JS).

For information on how to call JS functions from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Invoke a static .NET method

To invoke a static .NET method from JavaScript (JS), use the JS functions:

  • DotNet.invokeMethodAsync (Recommended): Asynchronous for both Blazor Server and Blazor WebAssembly apps.
  • DotNet.invokeMethod: Synchronous for Blazor WebAssembly apps only.

Pass in the name of the assembly containing the method, the identifier of the static .NET method, and any arguments.

In the following example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.
  • The ARGUMENTS placeholder are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
DotNet.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID', ARGUMENTS);

DotNet.invokeMethodAsync returns a JS Promise representing the result of the operation. DotNet.invokeMethod (Blazor WebAssembly only) returns the result of the operation.

Important

The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) to support Blazor Server scenarios.

The .NET method must be public, static, and have the [JSInvokable] attribute.

In the following example:

  • The <T> placeholder indicates the return type, which is only required for methods that return a value.
  • The .NET METHOD ID placeholder is the method identifier.
@code 
    [JSInvokable]
    public static Task<T> .NET METHOD ID()
    
        ...
    

Note

Calling open generic methods isn’t supported with static .NET methods but is supported with instance methods. For more information, see the Call .NET generic class methods section.

In the following CallDotNetExample1 component, the ReturnArrayAsync C# method returns an int array. The [JSInvokable] attribute is applied to the method, which makes the method invokable by JS.

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code 
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    
        return Task.FromResult(new int[]  1, 2, 3 );
    

The <button> element’s onclick HTML attribute is JavaScript’s onclick event handler assignment for processing click events, not Blazor’s @onclick directive attribute. The returnArrayAsync JS function is assigned as the handler.

The following returnArrayAsync JS function, calls the ReturnArrayAsync .NET method of the preceding CallDotNetExample1 component and logs the result to the browser’s web developer tools console. BlazorSample is the app’s assembly name.

<script>
  window.returnArrayAsync = () => 
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => 
        console.log(data);
      );
    ;
</script>

When the Trigger .NET static method button is selected, the browser’s developer tools console output displays the array data. The format of the output differs slightly among browsers. The following output shows the format used by Microsoft Edge:

Array(3) [ 1, 2, 3 ]

By default, the .NET method identifier for the JS call is the .NET method name, but you can specify a different identifier using the [JSInvokable] attribute constructor. In the following example, DifferentMethodName is the assigned method identifier for the ReturnArrayAsync method:

[JSInvokable("DifferentMethodName")]

In the call to DotNet.invokeMethodAsync or DotNet.invokeMethod (Blazor WebAssembly only), call DifferentMethodName to execute the ReturnArrayAsync .NET method:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (Blazor WebAssembly only)

Note

The ReturnArrayAsync method example in this section returns the result of a Task without the use of explicit C# async and await keywords. Coding methods with async and await is typical of methods that use the await keyword to return the value of asynchronous operations.

ReturnArrayAsync method composed with async and await keywords:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()

    return await Task.FromResult(new int[]  1, 2, 3 );

For more information, see Asynchronous programming with async and await in the C# guide.

Invoke an instance .NET method

To invoke an instance .NET method from JavaScript (JS):

  • Pass the .NET instance by reference to JS by wrapping the instance in a DotNetObjectReference and calling Create on it.
  • Invoke a .NET instance method from JS using invokeMethodAsync or invokeMethod (Blazor WebAssembly only) from the passed DotNetObjectReference. The .NET instance can also be passed as an argument when invoking other .NET methods from JS.
  • Dispose of the DotNetObjectReference.

The following sections of this article demonstrate various approaches for invoking an instance .NET method:

Pass a DotNetObjectReference to an individual JavaScript function

The example in this section demonstrates how to pass a DotNetObjectReference to an individual JavaScript (JS) function.

The following sayHello1 JS function receives a DotNetObjectReference and calls invokeMethodAsync to call the GetHelloMessage .NET method of a component:

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

For the following CallDotNetExample2 component:

  • The component has a JS-invokable .NET method named GetHelloMessage.
  • When the Trigger .NET instance method button is selected, the JS function sayHello1 is called with the DotNetObjectReference.
  • sayHello1:
    • Calls GetHelloMessage and receives the message result.
    • Returns the message result to the calling TriggerDotNetInstanceMethod method.
  • The returned message from sayHello1 in result is displayed to the user.
  • To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To pass arguments to an instance method:

  1. Add parameters to the .NET method invocation. In the following example, a name is passed to the method. Add additional parameters to the list as needed.

    <script>
      window.sayHello2 = (dotNetHelper, name) => 
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      ;
    </script>
    

    In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

  2. Provide the parameter list to the .NET method.

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, passedName!";

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

Pass a DotNetObjectReference to a class with multiple JavaScript functions

The example in this section demonstrates how to pass a DotNetObjectReference to a JavaScript (JS) class with multiple functions.

Create and pass a DotNetObjectReference from the OnAfterRenderAsync lifecycle method to a JS class for multiple functions to use. Make sure that the .NET code disposes of the DotNetObjectReference, as the following example shows.

In the following CallDotNetExampleOneHelper component, the Trigger JS function buttons call JS functions by setting the JS onclick property, not Blazor’s @onclick directive attribute.

Pages/CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button onclick="GreetingHelpers.sayHello()">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button onclick="GreetingHelpers.welcomeVisitor()">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code 
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    
        if (firstRender)
        
            dotNetHelper = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);
        
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, name!";

    public void Dispose()
    
        dotNetHelper?.Dispose();
    

In the preceding example:

  • JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.
  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.
  • The component must explicitly dispose of the DotNetObjectReference to permit garbage collection and prevent a memory leak.
<script>
  class GreetingHelpers 
    static dotNetHelper;

    static setDotNetHelper(value) 
      GreetingHelpers.dotNetHelper = value;
    

    static async sayHello() 
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
      alert(`Message from .NET: "$msg"`);
    

    static async welcomeVisitor() 
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
      alert(`Message from .NET: "$msg"`);
    
  

  window.GreetingHelpers = GreetingHelpers;
</script>

In the preceding example:

  • The GreetingHelpers class is added to the window object to globally define the class, which permits Blazor to locate the class for JS interop.
  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.

Call .NET generic class methods

JavaScript (JS) functions can call .NET generic class methods, where a JS function calls a .NET method of a generic class.

In the following generic type class (GenericType<TValue>):

  • The class has a single type parameter (TValue) with a single generic Value property.
  • The class has two non-generic methods marked with the [JSInvokable] attribute, each with a generic type parameter named newValue:
    • Update synchronously updates the value of Value from newValue.
    • UpdateAsync asynchronously updates the value of Value from newValue after creating an awaitable task with Task.Yield that asynchronously yields back to the current context when awaited.
  • Each of the class methods write the type of TValue and the value of Value to the console. Writing to the console is only for demonstration purposes. Production apps usually avoid writing to the console in favor of app logging. For more information, see ASP.NET Core Blazor logging and Logging in .NET Core and ASP.NET Core.

Note

Open generic types and methods don’t specify types for type placeholders. Conversely, closed generics supply types for all type placeholders. The examples in this section demonstrate closed generics, but invoking JS interop instance methods with open generics is supported. Use of open generics is not supported for static .NET method invocations, which were described earlier in this article.

For more information, see the following articles:

GenericType.cs:

using Microsoft.JSInterop;

public class GenericType<TValue>

    public TValue? Value  get; set; 

    [JSInvokable]
    public void Update(TValue newValue)
    
        Value = newValue;

        Console.WriteLine($"Update: GenericType<typeof(TValue)>: Value");
    

    [JSInvokable]
    public async void UpdateAsync(TValue newValue)
    
        await Task.Yield();
        Value = newValue;

        Console.WriteLine($"UpdateAsync: GenericType<typeof(TValue)>: Value");
    

In the following invokeMethodsAsync function:

  • The generic type class’s Update and UpdateAsync methods are called with arguments representing strings and numbers.
  • Blazor WebAssembly apps support calling .NET methods synchronously with invokeMethod. syncInterop receives a boolean value indicating if the JS interop is occurring in a Blazor WebAssembly app. When syncInterop is true, invokeMethod is safely called. If the value of syncInterop is false, only the asynchronous function invokeMethodAsync is called because the JS interop is executing in a Blazor Server app.
  • For demonstration purposes, the DotNetObjectReference function call (invokeMethod or invokeMethodAsync), the .NET method called (Update or UpdateAsync), and the argument are written to the console. The arguments use a random number to permit matching the JS function call to the .NET method invocation (also written to the console on the .NET side). Production code usually doesn’t write to the console, either on the client or the server. Production apps usually rely upon app logging. For more information, see ASP.NET Core Blazor logging and Logging in .NET Core and ASP.NET Core.
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

  window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => 
    var n = randomInt();
    console.log(`JS: invokeMethodAsync:Update('string $n')`);
    await dotNetHelper1.invokeMethodAsync('Update', `string $n`);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync('string $n')`);
    await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string $n`);

    if (syncInterop) 
      n = randomInt();
      console.log(`JS: invokeMethod:Update('string $n')`);
      dotNetHelper1.invokeMethod('Update', `string $n`);
    

    n = randomInt();
    console.log(`JS: invokeMethodAsync:Update($n)`);
    await dotNetHelper2.invokeMethodAsync('Update', n);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync($n)`);
    await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

    if (syncInterop) 
      n = randomInt();
      console.log(`JS: invokeMethod:Update($n)`);
      dotNetHelper2.invokeMethod('Update', n);
    
  ;
</script>

In the following GenericsExample component:

  • The JS function invokeMethodsAsync is called when the Invoke Interop button is selected.
  • A pair of DotNetObjectReference types are created and passed to the JS function for instances of the GenericType as a string and an int.

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JS

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code 
    private GenericType<string> genericType1 = new()  Value = "string 0" ;
    private GenericType<int> genericType2 = new()  Value = 0 ;

    public async Task InvokeInterop()
    
        var syncInterop = 
            RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync",
            syncInterop,
            DotNetObjectReference.Create(genericType1),
            DotNetObjectReference.Create(genericType2));
    

In the preceding example, JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.

The following demonstrates typical output of the preceding example when the Invoke Interop button is selected in a Blazor WebAssembly app:

JS: invokeMethodAsync:Update(‘string 37802’)
.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync(‘string 53051’)
JS: invokeMethod:Update(‘string 26784’)
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

If the preceding example is implemented in a Blazor Server app, the synchronous calls with invokeMethod are avoided. The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) in Blazor Server scenarios.

Typical output of a Blazor Server app:

JS: invokeMethodAsync:Update(‘string 34809’)
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync(‘string 93059’)
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

The preceding output examples demonstrate that asynchronous methods execute and complete in an arbitrary order depending on several factors, including thread scheduling and the speed of method execution. It isn’t possible to reliably predict the order of completion for asynchronous method calls.

Class instance examples

The following sayHello1 JS function:

  • Calls the GetHelloMessage .NET method on the passed DotNetObjectReference.
  • Returns the message from GetHelloMessage to the sayHello1 caller.
<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

The following HelloHelper class has a JS-invokable .NET method named GetHelloMessage. When HelloHelper is created, the name in the Name property is used to return a message from GetHelloMessage.

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper

    public HelloHelper(string? name)
    
        Name = name ?? "No Name";
    

    public string? Name  get; set; 

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, Name!";

The CallHelloHelperGetHelloMessage method in the following JsInteropClasses3 class invokes the JS function sayHello1 with a new instance of HelloHelper.

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable

    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper>? objRef;

    public JsInteropClasses3(IJSRuntime js)
    
        this.js = js;
    

    public ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

When the Trigger .NET instance method button is selected in the following CallDotNetExample4 component, JsInteropClasses3.CallHelloHelperGetHelloMessage is called with the value of name.

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    private async Task TriggerDotNetInstanceMethod()
    
        jsInteropClasses = new JsInteropClasses3(JS);
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    

    public void Dispose()
    
        jsInteropClasses?.Dispose();
    

The following image shows the rendered component with the name Amy Pond in the Name field. After the button is selected, Hello, Amy Pond! is displayed in the UI:

The preceding pattern shown in the JsInteropClasses3 class can also be implemented entirely in a component.

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<HelloHelper>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

The output displayed by the CallDotNetExample5 component is Hello, Amy Pond! when the name Amy Pond is provided in the Name field.

In the preceding CallDotNetExample5 component, the .NET object reference is disposed. If a class or component doesn’t dispose the DotNetObjectReference, dispose it from the client by calling dispose on the passed DotNetObjectReference:

window.jsFunction = (dotNetHelper) => 
  dotNetHelper.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID');
  dotNetHelper.dispose();

In the preceding example:

  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.
  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.

Component instance .NET method helper class

A helper class can invoke a .NET instance method as an Action. Helper classes are useful in the following scenarios:

  • When several components of the same type are rendered on the same page.
  • In Blazor Server apps with multiple users concurrently using the same component.

In the following example:

  • The CallDotNetExample6 component contains several ListItem components, which is a shared component in the app’s Shared folder.
  • Each ListItem component is composed of a message and a button.
  • When a ListItem component button is selected, that ListItem‘s UpdateMessage method changes the list item text and hides the button.

The following MessageUpdateInvokeHelper class maintains a JS-invokable .NET method, UpdateMessageCaller, to invoke the Action specified when the class is instantiated. BlazorSample is the app’s assembly name.

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper

    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    
        this.action = action;
    

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    
        action.Invoke();
    

The following updateMessageCaller JS function invokes the UpdateMessageCaller .NET method. BlazorSample is the app’s assembly name.

<script>
  window.updateMessageCaller = (dotNetHelper) => 
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

The following ListItem component is a shared component that can be used any number of times in a parent component and creates list items (<li>...</li>) for an HTML list (<ul>...</ul> or <ol>...</ol>). Each ListItem component instance establishes an instance of MessageUpdateInvokeHelper with an Action set to its UpdateMessage method.

When a ListItem component’s InteropCall button is selected, updateMessageCaller is invoked with a created DotNetObjectReference for the MessageUpdateInvokeHelper instance. This permits the framework to call UpdateMessageCaller on that ListItem‘s MessageUpdateInvokeHelper instance. The passed DotNetObjectReference is disposed in JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code 
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    

    protected async Task InteropCall()
    
        if (messageUpdateInvokeHelper is not null)
        
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        
    

    private void UpdateMessage()
    
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    

StateHasChanged is called to update the UI when message is set in UpdateMessage. If StateHasChanged isn’t called, Blazor has no way of knowing that the UI should be updated when the Action is invoked.

The following CallDotNetExample6 parent component includes four list items, each an instance of the ListItem component.

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

The following image shows the rendered CallDotNetExample6 parent component after the second InteropCall button is selected:

  • The second ListItem component has displayed the UpdateMessage Called! message.
  • The InteropCall button for the second ListItem component isn’t visible because the button’s CSS display property is set to none.

Rendered 'CallDotNetExample6' component example

Synchronous JS interop in Blazor WebAssembly apps

This section only applies to Blazor WebAssembly apps.

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, all JS interop calls must be asynchronous because they’re sent over a network connection.

If you know for certain that your app only ever runs on Blazor WebAssembly, you can choose to make synchronous JS interop calls. This has slightly less overhead than making asynchronous calls and can result in fewer render cycles because there’s no intermediate state while awaiting results.

To make a synchronous call from JavaScript to .NET in Blazor WebAssembly apps, use DotNet.invokeMethod instead of DotNet.invokeMethodAsync.

Synchronous calls work if:

  • The app is running on Blazor WebAssembly, not Blazor Server.
  • The called function returns a value synchronously. The function isn’t an async method and doesn’t return a .NET Task or JavaScript Promise.

Location of JavaScript

Load JavaScript (JS) code using any of approaches described by the JS interop overview article:

For information on isolating scripts in JS modules, see the JavaScript isolation in JavaScript modules section.

Warning

Don’t place a <script> tag in a component file (.razor) because the <script> tag can’t be updated dynamically.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

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.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Avoid circular object references

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.

Byte array support

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 .NET.

Provide a sendByteArray JS function. The function is called by a button in the component and doesn’t return a value:

<script>
  window.sendByteArray = () => 
    const data = new Uint8Array([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]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => 
        alert(str);
      );
  ;
</script>

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"
@using System.Text

<h1>Call .NET Example 7</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
    Quote &copy;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 
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    

For information on using a byte array when calling JavaScript from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Stream from JavaScript to .NET

Blazor supports streaming data directly from JavaScript to .NET. Streams are requested using the Microsoft.JSInterop.IJSStreamReference interface.

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync returns a Stream and uses the following parameters:

  • maxAllowedSize: Maximum number of bytes permitted for the read operation from JavaScript, which defaults to 512,000 bytes if not specified.
  • cancellationToken: A CancellationToken for cancelling the read.

In JavaScript:

function streamToDotNet() 
  return new Uint8Array(10000000);

In C# code:

var dataReference = 
    await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream = 
    await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

In the preceding example:

  • JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.
  • The dataReferenceStream is written to disk (file.txt) at the current user’s temporary folder path (GetTempPath).

Call JavaScript functions from .NET methods in ASP.NET Core Blazor covers the reverse operation, streaming from .NET to JavaScript using a DotNetStreamReference.

ASP.NET Core Blazor file uploads covers how to upload a file in Blazor.

Size limits on JavaScript interop calls

This section only applies to Blazor Server apps. In Blazor WebAssembly, the framework doesn’t impose a limit on the size of JavaScript (JS) interop inputs and outputs.

In Blazor Server, JS interop calls 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.

When SignalR logging isn’t set to Debug or Trace, a message size error only appears in the browser’s developer tools console:

Error: Connection disconnected with error ‘Error: Server returned an error on close: Connection closed with an error.’.

When SignalR server-side logging is set to Debug or Trace, server-side logging surfaces an InvalidDataException for a message size error.

appsettings.Development.json:


  "DetailedErrors": true,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    
  

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions.

Increase the limit by setting MaximumReceiveMessageSize in Program.cs. The following example sets the maximum receive message size to 64 KB (64 * 1024):

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Increasing the SignalR incoming message size limit comes at the cost of requiring more server resources, and it exposes the server to increased risks from a malicious user. Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties.

Consider the following guidance when developing code that transfers a large amount of data between JS and Blazor in Blazor Server apps:

  • Leverage the native streaming interop support to transfer data larger than the SignalR incoming message size limit:
  • General tips:
    • Don’t allocate large objects in JS and C# code.
    • Free consumed memory when the process is completed or cancelled.
    • Enforce the following additional requirements for security purposes:
      • Declare the maximum file or data size that can be passed.
      • Declare the minimum upload rate from the client to the server.
    • After the data is received by the server, the data can be:
      • Temporarily stored in a memory buffer until all of the segments are collected.
      • Consumed immediately. For example, the data can be stored immediately in a database or written to disk as each segment is received.

Additional resources

For information on how to call JS functions from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Invoke a static .NET method

To invoke a static .NET method from JavaScript (JS), use the JS functions:

  • DotNet.invokeMethodAsync (Recommended): Asynchronous for both Blazor Server and Blazor WebAssembly apps.
  • DotNet.invokeMethod: Synchronous for Blazor WebAssembly apps only.

In the following example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.
  • The ARGUMENTS placeholder are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
DotNet.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID', ARGUMENTS);

DotNet.invokeMethodAsync returns a JS Promise representing the result of the operation. DotNet.invokeMethod (Blazor WebAssembly only) returns the result of the operation.

Important

The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) to support Blazor Server scenarios.

The .NET method must be public, static, and have the [JSInvokable] attribute.

In the following example:

  • The <T> placeholder indicates the return type, which is only required for methods that return a value.
  • The .NET METHOD ID placeholder is the method identifier.
@code 
    [JSInvokable]
    public static Task<T> .NET METHOD ID()
    
        ...
    

Note

Calling open generic methods isn’t supported with static .NET methods but is supported with instance methods, which are described later in this article.

In the following CallDotNetExample1 component, the ReturnArrayAsync C# method returns an int array. The [JSInvokable] attribute is applied to the method, which makes the method invokable by JS.

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code 
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    
        return Task.FromResult(new int[]  1, 2, 3 );
    

The <button> element’s onclick HTML attribute is JavaScript’s onclick event handler assignment for processing click events, not Blazor’s @onclick directive attribute. The returnArrayAsync JS function is assigned as the handler.

The following returnArrayAsync JS function, calls the ReturnArrayAsync .NET method of the preceding CallDotNetExample1 component and logs the result to the browser’s web developer tools console. BlazorSample is the app’s assembly name.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.returnArrayAsync = () => 
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => 
        console.log(data);
      );
    ;
</script>

When the Trigger .NET static method button is selected, the browser’s developer tools console output displays the array data. The format of the output differs slightly among browsers. The following output shows the format used by Microsoft Edge:

Array(3) [ 1, 2, 3 ]

By default, the .NET method identifier for the JS call is the .NET method name, but you can specify a different identifier using the [JSInvokable] attribute constructor. In the following example, DifferentMethodName is the assigned method identifier for the ReturnArrayAsync method:

[JSInvokable("DifferentMethodName")]

In the call to DotNet.invokeMethodAsync or DotNet.invokeMethod (Blazor WebAssembly only), call DifferentMethodName to execute the ReturnArrayAsync .NET method:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (Blazor WebAssembly only)

Note

The ReturnArrayAsync method example in this section returns the result of a Task without the use of explicit C# async and await keywords. Coding methods with async and await is typical of methods that use the await keyword to return the value of asynchronous operations.

ReturnArrayAsync method composed with async and await keywords:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()

    return await Task.FromResult(new int[]  1, 2, 3 );

For more information, see Asynchronous programming with async and await in the C# guide.

Invoke an instance .NET method

To invoke an instance .NET method from JavaScript (JS):

  • Pass the .NET instance by reference to JS by wrapping the instance in a DotNetObjectReference and calling Create on it.
  • Invoke a .NET instance method from JS using invokeMethodAsync or invokeMethod (Blazor WebAssembly only) from the passed DotNetObjectReference. The .NET instance can also be passed as an argument when invoking other .NET methods from JS.
  • Dispose of the DotNetObjectReference.

The following sections of this article demonstrate various approaches for invoking an instance .NET method:

Component instance examples

The following sayHello1 JS function receives a DotNetObjectReference and calls invokeMethodAsync to call the GetHelloMessage .NET method of a component.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

For the following CallDotNetExample2 component:

  • The component has a JS-invokable .NET method named GetHelloMessage.
  • When the Trigger .NET instance method button is selected, the JS function sayHello1 is called with the DotNetObjectReference.
  • sayHello1:
    • Calls GetHelloMessage and receives the message result.
    • Returns the message result to the calling TriggerDotNetInstanceMethod method.
  • The returned message from sayHello1 in result is displayed to the user.
  • To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    public void Dispose()
    
        objRef?.Dispose();
    

To pass arguments to an instance method:

  1. Add parameters to the .NET method invocation. In the following example, a name is passed to the method. Add additional parameters to the list as needed.

    <script>
      window.sayHello2 = (dotNetHelper, name) => 
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      ;
    </script>
    
  2. Provide the parameter list to the .NET method.

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, passedName!";

    public void Dispose()
    
        objRef?.Dispose();
    

Class instance examples

The following sayHello1 JS function:

  • Calls the GetHelloMessage .NET method on the passed DotNetObjectReference.
  • Returns the message from GetHelloMessage to the sayHello1 caller.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

The following HelloHelper class has a JS-invokable .NET method named GetHelloMessage. When HelloHelper is created, the name in the Name property is used to return a message from GetHelloMessage.

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper

    public HelloHelper(string name)
    
        Name = name;
    

    public string Name  get; set; 

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, Name!";

The CallHelloHelperGetHelloMessage method in the following JsInteropClasses3 class invokes the JS function sayHello1 with a new instance of HelloHelper.

JsInteropClasses3.cs:

using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable

    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper> objRef;

    public JsInteropClasses3(IJSRuntime js)
    
        this.js = js;
    

    public ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

When the Trigger .NET instance method button is selected in the following CallDotNetExample4 component, JsInteropClasses3.CallHelloHelperGetHelloMessage is called with the value of name.

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    private async Task TriggerDotNetInstanceMethod()
    
        jsInteropClasses = new JsInteropClasses3(JS);
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    

    public void Dispose()
    
        jsInteropClasses?.Dispose();
    

The following image shows the rendered component with the name Amy Pond in the Name field. After the button is selected, Hello, Amy Pond! is displayed in the UI:

The preceding pattern shown in the JsInteropClasses3 class can also be implemented entirely in a component.

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<HelloHelper> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

The output displayed by the CallDotNetExample5 component is Hello, Amy Pond! when the name Amy Pond is provided in the Name field.

In the preceding CallDotNetExample5 component, the .NET object reference is disposed. If a class or component doesn’t dispose the DotNetObjectReference, dispose it from the client by calling dispose on the passed DotNetObjectReference:

window.jsFunction = (dotNetHelper) => 
  dotNetHelper.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID');
  dotNetHelper.dispose();

In the preceding example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.

Component instance .NET method helper class

A helper class can invoke a .NET instance method as an Action. Helper classes are useful in the following scenarios:

  • When several components of the same type are rendered on the same page.
  • In Blazor Server apps with multiple users concurrently using the same component.

In the following example:

  • The CallDotNetExample6 component contains several ListItem components, which is a shared component in the app’s Shared folder.
  • Each ListItem component is composed of a message and a button.
  • When a ListItem component button is selected, that ListItem‘s UpdateMessage method changes the list item text and hides the button.

The following MessageUpdateInvokeHelper class maintains a JS-invokable .NET method, UpdateMessageCaller, to invoke the Action specified when the class is instantiated. BlazorSample is the app’s assembly name.

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper

    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    
        this.action = action;
    

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    
        action.Invoke();
    

The following updateMessageCaller JS function invokes the UpdateMessageCaller .NET method. BlazorSample is the app’s assembly name.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.updateMessageCaller = (dotNetHelper) => 
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  
</script>

The following ListItem component is a shared component that can be used any number of times in a parent component and creates list items (<li>...</li>) for an HTML list (<ul>...</ul> or <ol>...</ol>). Each ListItem component instance establishes an instance of MessageUpdateInvokeHelper with an Action set to its UpdateMessage method.

When a ListItem component’s InteropCall button is selected, updateMessageCaller is invoked with a created DotNetObjectReference for the MessageUpdateInvokeHelper instance. This permits the framework to call UpdateMessageCaller on that ListItem‘s MessageUpdateInvokeHelper instance. The passed DotNetObjectReference is disposed in JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code 
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    

    protected async Task InteropCall()
    
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    

    private void UpdateMessage()
    
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    

StateHasChanged is called to update the UI when message is set in UpdateMessage. If StateHasChanged isn’t called, Blazor has no way of knowing that the UI should be updated when the Action is invoked.

The following CallDotNetExample6 parent component includes four list items, each an instance of the ListItem component.

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

The following image shows the rendered CallDotNetExample6 parent component after the second InteropCall button is selected:

  • The second ListItem component has displayed the UpdateMessage Called! message.
  • The InteropCall button for the second ListItem component isn’t visible because the button’s CSS display property is set to none.

Rendered 'CallDotNetExample6' component example

Location of JavaScript

Load JavaScript (JS) code using any of approaches described by the JS interop overview article:

For information on isolating scripts in JS modules, see the JavaScript isolation in JavaScript modules section.

Warning

Don’t place a <script> tag in a component file (.razor) because the <script> tag can’t be updated dynamically.

Avoid circular object references

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.

Size limits on JavaScript interop calls

This section only applies to Blazor Server apps. In Blazor WebAssembly, the framework doesn’t impose a limit on the size of JavaScript (JS) interop inputs and outputs.

In Blazor Server, JS interop calls 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.

When SignalR logging isn’t set to Debug or Trace, a message size error only appears in the browser’s developer tools console:

Error: Connection disconnected with error ‘Error: Server returned an error on close: Connection closed with an error.’.

When SignalR server-side logging is set to Debug or Trace, server-side logging surfaces an InvalidDataException for a message size error.

appsettings.Development.json:


  "DetailedErrors": true,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    
  

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions.

Increase the limit by setting MaximumReceiveMessageSize in Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Increasing the SignalR incoming message size limit comes at the cost of requiring more server resources, and it exposes the server to increased risks from a malicious user. Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties.

One option for reading large payloads is to send the content in smaller chunks and process the payload as a Stream. This can be used when reading large JSON payloads or if data is available in JS as raw bytes. For an example that demonstrates sending large binary payloads in Blazor Server that uses techniques similar to the InputFile component, see the Binary Submit sample app.

Consider the following guidance when developing code that transfers a large amount of data between JS and Blazor in Blazor Server apps:

  • Slice the data into smaller pieces, and send the data segments sequentially until all of the data is received by the server.
  • Don’t allocate large objects in JS and C# code.
  • Don’t block the main UI thread for long periods when sending or receiving data.
  • Free consumed memory when the process is completed or cancelled.
  • Enforce the following additional requirements for security purposes:
    • Declare the maximum file or data size that can be passed.
    • Declare the minimum upload rate from the client to the server.
  • After the data is received by the server, the data can be:
    • Temporarily stored in a memory buffer until all of the segments are collected.
    • Consumed immediately. For example, the data can be stored immediately in a database or written to disk as each segment is received.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

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.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Additional resources

For information on how to call JS functions from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Invoke a static .NET method

To invoke a static .NET method from JavaScript (JS), use the JS functions:

  • DotNet.invokeMethodAsync (Recommended): Asynchronous for both Blazor Server and Blazor WebAssembly apps.
  • DotNet.invokeMethod: Synchronous for Blazor WebAssembly apps only.

In the following example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.
  • The ARGUMENTS placeholder are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
DotNet.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID', ARGUMENTS);

DotNet.invokeMethodAsync returns a JS Promise representing the result of the operation. DotNet.invokeMethod (Blazor WebAssembly only) returns the result of the operation.

Important

The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) to support Blazor Server scenarios.

The .NET method must be public, static, and have the [JSInvokable] attribute.

In the following example:

  • The <T> placeholder indicates the return type, which is only required for methods that return a value.
  • The .NET METHOD ID placeholder is the method identifier.
@code 
    [JSInvokable]
    public static Task<T> .NET METHOD ID()
    
        ...
    

Note

Calling open generic methods isn’t supported with static .NET methods but is supported with instance methods, which are described later in this article.

In the following CallDotNetExample1 component, the ReturnArrayAsync C# method returns an int array. The [JSInvokable] attribute is applied to the method, which makes the method invokable by JS.

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code 
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    
        return Task.FromResult(new int[]  1, 2, 3 );
    

The <button> element’s onclick HTML attribute is JavaScript’s onclick event handler assignment for processing click events, not Blazor’s @onclick directive attribute. The returnArrayAsync JS function is assigned as the handler.

The following returnArrayAsync JS function, calls the ReturnArrayAsync .NET method of the preceding CallDotNetExample1 component and logs the result to the browser’s web developer tools console. BlazorSample is the app’s assembly name.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.returnArrayAsync = () => 
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => 
        console.log(data);
      );
    ;
</script>

When the Trigger .NET static method button is selected, the browser’s developer tools console output displays the array data. The format of the output differs slightly among browsers. The following output shows the format used by Microsoft Edge:

Array(3) [ 1, 2, 3 ]

By default, the .NET method identifier for the JS call is the .NET method name, but you can specify a different identifier using the [JSInvokable] attribute constructor. In the following example, DifferentMethodName is the assigned method identifier for the ReturnArrayAsync method:

[JSInvokable("DifferentMethodName")]

In the call to DotNet.invokeMethodAsync or DotNet.invokeMethod (Blazor WebAssembly only), call DifferentMethodName to execute the ReturnArrayAsync .NET method:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (Blazor WebAssembly only)

Note

The ReturnArrayAsync method example in this section returns the result of a Task without the use of explicit C# async and await keywords. Coding methods with async and await is typical of methods that use the await keyword to return the value of asynchronous operations.

ReturnArrayAsync method composed with async and await keywords:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()

    return await Task.FromResult(new int[]  1, 2, 3 );

For more information, see Asynchronous programming with async and await in the C# guide.

Invoke an instance .NET method

To invoke an instance .NET method from JavaScript (JS):

  • Pass the .NET instance by reference to JS by wrapping the instance in a DotNetObjectReference and calling Create on it.
  • Invoke a .NET instance method from JS using invokeMethodAsync or invokeMethod (Blazor WebAssembly only) from the passed DotNetObjectReference. The .NET instance can also be passed as an argument when invoking other .NET methods from JS.
  • Dispose of the DotNetObjectReference.

The following sections of this article demonstrate various approaches for invoking an instance .NET method:

Component instance examples

The following sayHello1 JS function receives a DotNetObjectReference and calls invokeMethodAsync to call the GetHelloMessage .NET method of a component.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

For the following CallDotNetExample2 component:

  • The component has a JS-invokable .NET method named GetHelloMessage.
  • When the Trigger .NET instance method button is selected, the JS function sayHello1 is called with the DotNetObjectReference.
  • sayHello1:
    • Calls GetHelloMessage and receives the message result.
    • Returns the message result to the calling TriggerDotNetInstanceMethod method.
  • The returned message from sayHello1 in result is displayed to the user.
  • To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    public void Dispose()
    
        objRef?.Dispose();
    

To pass arguments to an instance method:

  1. Add parameters to the .NET method invocation. In the following example, a name is passed to the method. Add additional parameters to the list as needed.

    <script>
      window.sayHello2 = (dotNetHelper, name) => 
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      ;
    </script>
    
  2. Provide the parameter list to the .NET method.

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, passedName!";

    public void Dispose()
    
        objRef?.Dispose();
    

Class instance examples

The following sayHello1 JS function:

  • Calls the GetHelloMessage .NET method on the passed DotNetObjectReference.
  • Returns the message from GetHelloMessage to the sayHello1 caller.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

The following HelloHelper class has a JS-invokable .NET method named GetHelloMessage. When HelloHelper is created, the name in the Name property is used to return a message from GetHelloMessage.

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper

    public HelloHelper(string name)
    
        Name = name;
    

    public string Name  get; set; 

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, Name!";

The CallHelloHelperGetHelloMessage method in the following JsInteropClasses3 class invokes the JS function sayHello1 with a new instance of HelloHelper.

JsInteropClasses3.cs:

using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable

    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper> objRef;

    public JsInteropClasses3(IJSRuntime js)
    
        this.js = js;
    

    public ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

When the Trigger .NET instance method button is selected in the following CallDotNetExample4 component, JsInteropClasses3.CallHelloHelperGetHelloMessage is called with the value of name.

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    private async Task TriggerDotNetInstanceMethod()
    
        jsInteropClasses = new JsInteropClasses3(JS);
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    

    public void Dispose()
    
        jsInteropClasses?.Dispose();
    

The following image shows the rendered component with the name Amy Pond in the Name field. After the button is selected, Hello, Amy Pond! is displayed in the UI:

The preceding pattern shown in the JsInteropClasses3 class can also be implemented entirely in a component.

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string name;
    private string result;
    private DotNetObjectReference<HelloHelper> objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

The output displayed by the CallDotNetExample5 component is Hello, Amy Pond! when the name Amy Pond is provided in the Name field.

In the preceding CallDotNetExample5 component, the .NET object reference is disposed. If a class or component doesn’t dispose the DotNetObjectReference, dispose it from the client by calling dispose on the passed DotNetObjectReference:

window.jsFunction = (dotNetHelper) => 
  dotNetHelper.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID');
  dotNetHelper.dispose();

In the preceding example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.

Component instance .NET method helper class

A helper class can invoke a .NET instance method as an Action. Helper classes are useful in the following scenarios:

  • When several components of the same type are rendered on the same page.
  • In Blazor Server apps with multiple users concurrently using the same component.

In the following example:

  • The CallDotNetExample6 component contains several ListItem components, which is a shared component in the app’s Shared folder.
  • Each ListItem component is composed of a message and a button.
  • When a ListItem component button is selected, that ListItem‘s UpdateMessage method changes the list item text and hides the button.

The following MessageUpdateInvokeHelper class maintains a JS-invokable .NET method, UpdateMessageCaller, to invoke the Action specified when the class is instantiated. BlazorSample is the app’s assembly name.

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper

    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    
        this.action = action;
    

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    
        action.Invoke();
    

The following updateMessageCaller JS function invokes the UpdateMessageCaller .NET method. BlazorSample is the app’s assembly name.

Inside the closing </body> tag of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<script>
  window.updateMessageCaller = (dotNetHelper) => 
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  
</script>

The following ListItem component is a shared component that can be used any number of times in a parent component and creates list items (<li>...</li>) for an HTML list (<ul>...</ul> or <ol>...</ol>). Each ListItem component instance establishes an instance of MessageUpdateInvokeHelper with an Action set to its UpdateMessage method.

When a ListItem component’s InteropCall button is selected, updateMessageCaller is invoked with a created DotNetObjectReference for the MessageUpdateInvokeHelper instance. This permits the framework to call UpdateMessageCaller on that ListItem‘s MessageUpdateInvokeHelper instance. The passed DotNetObjectReference is disposed in JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code 
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    

    protected async Task InteropCall()
    
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    

    private void UpdateMessage()
    
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    

StateHasChanged is called to update the UI when message is set in UpdateMessage. If StateHasChanged isn’t called, Blazor has no way of knowing that the UI should be updated when the Action is invoked.

The following CallDotNetExample6 parent component includes four list items, each an instance of the ListItem component.

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

The following image shows the rendered CallDotNetExample6 parent component after the second InteropCall button is selected:

  • The second ListItem component has displayed the UpdateMessage Called! message.
  • The InteropCall button for the second ListItem component isn’t visible because the button’s CSS display property is set to none.

Rendered 'CallDotNetExample6' component example

Location of JavaScript

Load JavaScript (JS) code using any of approaches described by the JS interop overview article:

Warning

Don’t place a <script> tag in a component file (.razor) because the <script> tag can’t be updated dynamically.

Avoid circular object references

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.

Size limits on JavaScript interop calls

This section only applies to Blazor Server apps. In Blazor WebAssembly, the framework doesn’t impose a limit on the size of JavaScript (JS) interop inputs and outputs.

In Blazor Server, JS interop calls 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.

When SignalR logging isn’t set to Debug or Trace, a message size error only appears in the browser’s developer tools console:

Error: Connection disconnected with error ‘Error: Server returned an error on close: Connection closed with an error.’.

When SignalR server-side logging is set to Debug or Trace, server-side logging surfaces an InvalidDataException for a message size error.

appsettings.Development.json:


  "DetailedErrors": true,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    
  

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions.

Increase the limit by setting MaximumReceiveMessageSize in Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Increasing the SignalR incoming message size limit comes at the cost of requiring more server resources, and it exposes the server to increased risks from a malicious user. Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties.

One option for reading large payloads is to send the content in smaller chunks and process the payload as a Stream. This can be used when reading large JSON payloads or if data is available in JS as raw bytes. For an example that demonstrates sending large binary payloads in Blazor Server that uses techniques similar to the InputFile component, see the Binary Submit sample app.

Consider the following guidance when developing code that transfers a large amount of data between JS and Blazor in Blazor Server apps:

  • Slice the data into smaller pieces, and send the data segments sequentially until all of the data is received by the server.
  • Don’t allocate large objects in JS and C# code.
  • Don’t block the main UI thread for long periods when sending or receiving data.
  • Free consumed memory when the process is completed or cancelled.
  • Enforce the following additional requirements for security purposes:
    • Declare the maximum file or data size that can be passed.
    • Declare the minimum upload rate from the client to the server.
  • After the data is received by the server, the data can be:
    • Temporarily stored in a memory buffer until all of the segments are collected.
    • Consumed immediately. For example, the data can be stored immediately in a database or written to disk as each segment is received.

Additional resources

For information on how to call JS functions from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Invoke a static .NET method

To invoke a static .NET method from JavaScript (JS), use the JS functions:

  • DotNet.invokeMethodAsync (Recommended): Asynchronous for both Blazor Server and Blazor WebAssembly apps.
  • DotNet.invokeMethod: Synchronous for Blazor WebAssembly apps only.

Pass in the name of the assembly containing the method, the identifier of the static .NET method, and any arguments.

In the following example:

  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.
  • The ARGUMENTS placeholder are optional, comma-separated arguments to pass to the method, each of which must be JSON-serializable.
DotNet.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID', ARGUMENTS);

DotNet.invokeMethodAsync returns a JS Promise representing the result of the operation. DotNet.invokeMethod (Blazor WebAssembly only) returns the result of the operation.

Important

The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) to support Blazor Server scenarios.

The .NET method must be public, static, and have the [JSInvokable] attribute.

In the following example:

  • The <T> placeholder indicates the return type, which is only required for methods that return a value.
  • The .NET METHOD ID placeholder is the method identifier.
@code 
    [JSInvokable]
    public static Task<T> .NET METHOD ID()
    
        ...
    

Note

Calling open generic methods isn’t supported with static .NET methods but is supported with instance methods. For more information, see the Call .NET generic class methods section.

In the following CallDotNetExample1 component, the ReturnArrayAsync C# method returns an int array. The [JSInvokable] attribute is applied to the method, which makes the method invokable by JS.

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code 
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    
        return Task.FromResult(new int[]  1, 2, 3 );
    

The <button> element’s onclick HTML attribute is JavaScript’s onclick event handler assignment for processing click events, not Blazor’s @onclick directive attribute. The returnArrayAsync JS function is assigned as the handler.

The following returnArrayAsync JS function, calls the ReturnArrayAsync .NET method of the preceding CallDotNetExample1 component and logs the result to the browser’s web developer tools console. BlazorSample is the app’s assembly name.

<script>
  window.returnArrayAsync = () => 
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => 
        console.log(data);
      );
    ;
</script>

When the Trigger .NET static method button is selected, the browser’s developer tools console output displays the array data. The format of the output differs slightly among browsers. The following output shows the format used by Microsoft Edge:

Array(3) [ 1, 2, 3 ]

By default, the .NET method identifier for the JS call is the .NET method name, but you can specify a different identifier using the [JSInvokable] attribute constructor. In the following example, DifferentMethodName is the assigned method identifier for the ReturnArrayAsync method:

[JSInvokable("DifferentMethodName")]

In the call to DotNet.invokeMethodAsync or DotNet.invokeMethod (Blazor WebAssembly only), call DifferentMethodName to execute the ReturnArrayAsync .NET method:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (Blazor WebAssembly only)

Note

The ReturnArrayAsync method example in this section returns the result of a Task without the use of explicit C# async and await keywords. Coding methods with async and await is typical of methods that use the await keyword to return the value of asynchronous operations.

ReturnArrayAsync method composed with async and await keywords:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()

    return await Task.FromResult(new int[]  1, 2, 3 );

For more information, see Asynchronous programming with async and await in the C# guide.

Invoke an instance .NET method

To invoke an instance .NET method from JavaScript (JS):

  • Pass the .NET instance by reference to JS by wrapping the instance in a DotNetObjectReference and calling Create on it.
  • Invoke a .NET instance method from JS using invokeMethodAsync or invokeMethod (Blazor WebAssembly only) from the passed DotNetObjectReference. The .NET instance can also be passed as an argument when invoking other .NET methods from JS.
  • Dispose of the DotNetObjectReference.

The following sections of this article demonstrate various approaches for invoking an instance .NET method:

Pass a DotNetObjectReference to an individual JavaScript function

The example in this section demonstrates how to pass a DotNetObjectReference to an individual JavaScript (JS) function.

The following sayHello1 JS function receives a DotNetObjectReference and calls invokeMethodAsync to call the GetHelloMessage .NET method of a component:

<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

For the following CallDotNetExample2 component:

  • The component has a JS-invokable .NET method named GetHelloMessage.
  • When the Trigger .NET instance method button is selected, the JS function sayHello1 is called with the DotNetObjectReference.
  • sayHello1:
    • Calls GetHelloMessage and receives the message result.
    • Returns the message result to the calling TriggerDotNetInstanceMethod method.
  • The returned message from sayHello1 in result is displayed to the user.
  • To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To pass arguments to an instance method:

  1. Add parameters to the .NET method invocation. In the following example, a name is passed to the method. Add additional parameters to the list as needed.

    <script>
      window.sayHello2 = (dotNetHelper, name) => 
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      ;
    </script>
    

    In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

  2. Provide the parameter list to the .NET method.

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, passedName!";

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

Pass a DotNetObjectReference to a class with multiple JavaScript functions

The example in this section demonstrates how to pass a DotNetObjectReference to a JavaScript (JS) class with multiple functions.

Create and pass a DotNetObjectReference from the OnAfterRenderAsync lifecycle method to a JS class for multiple functions to use. Make sure that the .NET code disposes of the DotNetObjectReference, as the following example shows.

In the following CallDotNetExampleOneHelper component, the Trigger JS function buttons call JS functions by setting the JS onclick property, not Blazor’s @onclick directive attribute.

Pages/CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button onclick="GreetingHelpers.sayHello()">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button onclick="GreetingHelpers.welcomeVisitor()">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code 
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    
        if (firstRender)
        
            dotNetHelper = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);
        
    

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, name!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, name!";

    public void Dispose()
    
        dotNetHelper?.Dispose();
    

In the preceding example:

  • JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.
  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.
  • The component must explicitly dispose of the DotNetObjectReference to permit garbage collection and prevent a memory leak.
<script>
  class GreetingHelpers 
    static dotNetHelper;

    static setDotNetHelper(value) 
      GreetingHelpers.dotNetHelper = value;
    

    static async sayHello() 
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
      alert(`Message from .NET: "$msg"`);
    

    static async welcomeVisitor() 
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
      alert(`Message from .NET: "$msg"`);
    
  

  window.GreetingHelpers = GreetingHelpers;
</script>

In the preceding example:

  • The GreetingHelpers class is added to the window object to globally define the class, which permits Blazor to locate the class for JS interop.
  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.

Call .NET generic class methods

JavaScript (JS) functions can call .NET generic class methods, where a JS function calls a .NET method of a generic class.

In the following generic type class (GenericType<TValue>):

  • The class has a single type parameter (TValue) with a single generic Value property.
  • The class has two non-generic methods marked with the [JSInvokable] attribute, each with a generic type parameter named newValue:
    • Update synchronously updates the value of Value from newValue.
    • UpdateAsync asynchronously updates the value of Value from newValue after creating an awaitable task with Task.Yield that asynchronously yields back to the current context when awaited.
  • Each of the class methods write the type of TValue and the value of Value to the console. Writing to the console is only for demonstration purposes. Production apps usually avoid writing to the console in favor of app logging. For more information, see ASP.NET Core Blazor logging and Logging in .NET Core and ASP.NET Core.

Note

Open generic types and methods don’t specify types for type placeholders. Conversely, closed generics supply types for all type placeholders. The examples in this section demonstrate closed generics, but invoking JS interop instance methods with open generics is supported. Use of open generics is not supported for static .NET method invocations, which were described earlier in this article.

For more information, see the following articles:

GenericType.cs:

using Microsoft.JSInterop;

public class GenericType<TValue>

    public TValue? Value  get; set; 

    [JSInvokable]
    public void Update(TValue newValue)
    
        Value = newValue;

        Console.WriteLine($"Update: GenericType<typeof(TValue)>: Value");
    

    [JSInvokable]
    public async void UpdateAsync(TValue newValue)
    
        await Task.Yield();
        Value = newValue;

        Console.WriteLine($"UpdateAsync: GenericType<typeof(TValue)>: Value");
    

In the following invokeMethodsAsync function:

  • The generic type class’s Update and UpdateAsync methods are called with arguments representing strings and numbers.
  • Blazor WebAssembly apps support calling .NET methods synchronously with invokeMethod. syncInterop receives a boolean value indicating if the JS interop is occurring in a Blazor WebAssembly app. When syncInterop is true, invokeMethod is safely called. If the value of syncInterop is false, only the asynchronous function invokeMethodAsync is called because the JS interop is executing in a Blazor Server app.
  • For demonstration purposes, the DotNetObjectReference function call (invokeMethod or invokeMethodAsync), the .NET method called (Update or UpdateAsync), and the argument are written to the console. The arguments use a random number to permit matching the JS function call to the .NET method invocation (also written to the console on the .NET side). Production code usually doesn’t write to the console, either on the client or the server. Production apps usually rely upon app logging. For more information, see ASP.NET Core Blazor logging and Logging in .NET Core and ASP.NET Core.
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

  window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => 
    var n = randomInt();
    console.log(`JS: invokeMethodAsync:Update('string $n')`);
    await dotNetHelper1.invokeMethodAsync('Update', `string $n`);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync('string $n')`);
    await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string $n`);

    if (syncInterop) 
      n = randomInt();
      console.log(`JS: invokeMethod:Update('string $n')`);
      dotNetHelper1.invokeMethod('Update', `string $n`);
    

    n = randomInt();
    console.log(`JS: invokeMethodAsync:Update($n)`);
    await dotNetHelper2.invokeMethodAsync('Update', n);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync($n)`);
    await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

    if (syncInterop) 
      n = randomInt();
      console.log(`JS: invokeMethod:Update($n)`);
      dotNetHelper2.invokeMethod('Update', n);
    
  ;
</script>

In the following GenericsExample component:

  • The JS function invokeMethodsAsync is called when the Invoke Interop button is selected.
  • A pair of DotNetObjectReference types are created and passed to the JS function for instances of the GenericType as a string and an int.

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JS

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code 
    private GenericType<string> genericType1 = new()  Value = "string 0" ;
    private GenericType<int> genericType2 = new()  Value = 0 ;

    public async Task InvokeInterop()
    
        var syncInterop = 
            RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync",
            syncInterop,
            DotNetObjectReference.Create(genericType1),
            DotNetObjectReference.Create(genericType2));
    

In the preceding example, JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.

The following demonstrates typical output of the preceding example when the Invoke Interop button is selected in a Blazor WebAssembly app:

JS: invokeMethodAsync:Update(‘string 37802’)
.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync(‘string 53051’)
JS: invokeMethod:Update(‘string 26784’)
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

If the preceding example is implemented in a Blazor Server app, the synchronous calls with invokeMethod are avoided. The asynchronous function (invokeMethodAsync) is preferred over the synchronous version (invokeMethod) in Blazor Server scenarios.

Typical output of a Blazor Server app:

JS: invokeMethodAsync:Update(‘string 34809’)
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync(‘string 93059’)
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

The preceding output examples demonstrate that asynchronous methods execute and complete in an arbitrary order depending on several factors, including thread scheduling and the speed of method execution. It isn’t possible to reliably predict the order of completion for asynchronous method calls.

Class instance examples

The following sayHello1 JS function:

  • Calls the GetHelloMessage .NET method on the passed DotNetObjectReference.
  • Returns the message from GetHelloMessage to the sayHello1 caller.
<script>
  window.sayHello1 = (dotNetHelper) => 
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  ;
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

The following HelloHelper class has a JS-invokable .NET method named GetHelloMessage. When HelloHelper is created, the name in the Name property is used to return a message from GetHelloMessage.

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper

    public HelloHelper(string? name)
    
        Name = name ?? "No Name";
    

    public string? Name  get; set; 

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, Name!";

The CallHelloHelperGetHelloMessage method in the following JsInteropClasses3 class invokes the JS function sayHello1 with a new instance of HelloHelper.

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable

    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper>? objRef;

    public JsInteropClasses3(IJSRuntime js)
    
        this.js = js;
    

    public ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

When the Trigger .NET instance method button is selected in the following CallDotNetExample4 component, JsInteropClasses3.CallHelloHelperGetHelloMessage is called with the value of name.

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    private async Task TriggerDotNetInstanceMethod()
    
        jsInteropClasses = new JsInteropClasses3(JS);
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    

    public void Dispose()
    
        jsInteropClasses?.Dispose();
    

The following image shows the rendered component with the name Amy Pond in the Name field. After the button is selected, Hello, Amy Pond! is displayed in the UI:

The preceding pattern shown in the JsInteropClasses3 class can also be implemented entirely in a component.

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code 
    private string? name;
    private string? result;
    private DotNetObjectReference<HelloHelper>? objRef;

    public async Task TriggerDotNetInstanceMethod()
    
        objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    

    public void Dispose()
    
        objRef?.Dispose();
    

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

To avoid a memory leak and allow garbage collection, the .NET object reference created by DotNetObjectReference is disposed in the Dispose method.

The output displayed by the CallDotNetExample5 component is Hello, Amy Pond! when the name Amy Pond is provided in the Name field.

In the preceding CallDotNetExample5 component, the .NET object reference is disposed. If a class or component doesn’t dispose the DotNetObjectReference, dispose it from the client by calling dispose on the passed DotNetObjectReference:

window.jsFunction = (dotNetHelper) => 
  dotNetHelper.invokeMethodAsync('ASSEMBLY NAME', '.NET METHOD ID');
  dotNetHelper.dispose();

In the preceding example:

  • The variable name dotNetHelper is arbitrary and can be changed to any preferred name.
  • The ASSEMBLY NAME placeholder is the app’s assembly name.
  • The .NET METHOD ID placeholder is the .NET method identifier.

Component instance .NET method helper class

A helper class can invoke a .NET instance method as an Action. Helper classes are useful in the following scenarios:

  • When several components of the same type are rendered on the same page.
  • In Blazor Server apps with multiple users concurrently using the same component.

In the following example:

  • The CallDotNetExample6 component contains several ListItem1 components, which is a shared component in the app’s Shared folder.
  • Each ListItem1 component is composed of a message and a button.
  • When a ListItem1 component button is selected, that ListItem1‘s UpdateMessage method changes the list item text and hides the button.

The following MessageUpdateInvokeHelper class maintains a JS-invokable .NET method, UpdateMessageCaller, to invoke the Action specified when the class is instantiated. BlazorSample is the app’s assembly name.

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper

    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    
        this.action = action;
    

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    
        action.Invoke();
    

The following updateMessageCaller JS function invokes the UpdateMessageCaller .NET method. BlazorSample is the app’s assembly name.

<script>
  window.updateMessageCaller = (dotNetHelper) => 
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

The following ListItem1 component is a shared component that can be used any number of times in a parent component and creates list items (<li>...</li>) for an HTML list (<ul>...</ul> or <ol>...</ol>). Each ListItem1 component instance establishes an instance of MessageUpdateInvokeHelper with an Action set to its UpdateMessage method.

When a ListItem1 component’s InteropCall button is selected, updateMessageCaller is invoked with a created DotNetObjectReference for the MessageUpdateInvokeHelper instance. This permits the framework to call UpdateMessageCaller on that ListItem1‘s MessageUpdateInvokeHelper instance. The passed DotNetObjectReference is disposed in JS (dotNetHelper.dispose()).

Shared/ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code 
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    

    protected async Task InteropCall()
    
        if (messageUpdateInvokeHelper is not null)
        
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        
    

    private void UpdateMessage()
    
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    

StateHasChanged is called to update the UI when message is set in UpdateMessage. If StateHasChanged isn’t called, Blazor has no way of knowing that the UI should be updated when the Action is invoked.

The following CallDotNetExample6 parent component includes four list items, each an instance of the ListItem1 component.

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

The following image shows the rendered CallDotNetExample6 parent component after the second InteropCall button is selected:

  • The second ListItem1 component has displayed the UpdateMessage Called! message.
  • The InteropCall button for the second ListItem1 component isn’t visible because the button’s CSS display property is set to none.

Rendered 'CallDotNetExample6' component example

Component instance .NET method called from DotNetObjectReference assigned to an element property

The assignment of a DotNetObjectReference to a property of an HTML element permits calling .NET methods on a component instance:

Similar to the approach described in the Component instance .NET method helper class section, this approach is useful in the following scenarios:

  • When several components of the same type are rendered on the same page.
  • In Blazor Server apps with multiple users concurrently using the same component.
  • The .NET method is invoked from a JS event (for example, onclick), not from a Blazor event (for example, @onclick).

In the following example:

  • The CallDotNetExample7 component contains several ListItem2 components, which is a shared component in the app’s Shared folder.
  • Each ListItem2 component is composed of a list item message <span> and a second <span> with a display CSS property set to inline-block for display.
  • When a ListItem2 component list item is selected, that ListItem2‘s UpdateMessage method changes the list item text in the first <span> and hides the second <span> by setting its display property to none.

The following assignDotNetHelper JS function assigns the DotNetObjectReference to an element in a property named dotNetHelper:

<script>
  window.assignDotNetHelper = (element, dotNetHelper) => 
    element.dotNetHelper = dotNetHelper;
  
</script>

The following interopCall JS function uses the DotNetObjectReference for the passed element to invoke a .NET method named UpdateMessage:

<script>
  window.interopCall = async (element) => 
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  
</script>

In the preceding example, the variable name dotNetHelper is arbitrary and can be changed to any preferred name.

The following ListItem2 component is a shared component that can be used any number of times in a parent component and creates list items (<li>...</li>) for an HTML list (<ul>...</ul> or <ol>...</ol>).

Each ListItem2 component instance invokes the assignDotNetHelper JS function in OnAfterRenderAsync with an element reference (the first <span> element of the list item) and the component instance as a DotNetObjectReference.

When a ListItem2 component’s message <span> is selected, interopCall is invoked passing the <span> element as a parameter (this), which invokes the UpdateMessage .NET method. In UpdateMessage, StateHasChanged is called to update the UI when message is set and the display property of the second <span> is updated. If StateHasChanged isn’t called, Blazor has no way of knowing that the UI should be updated when the method is invoked.

The DotNetObjectReference is disposed when the component is disposed.

Shared/ListItem2.razor:

@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code 
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    
        if (firstRender)
        
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        
    

    [JSInvokable]
    public void UpdateMessage()
    
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    

    public void Dispose() => objRef?.Dispose();

The following CallDotNetExample7 parent component includes four list items, each an instance of the ListItem2 component.

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

Synchronous JS interop in Blazor WebAssembly apps

This section only applies to Blazor WebAssembly apps.

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, all JS interop calls must be asynchronous because they’re sent over a network connection.

If you know for certain that your app only ever runs on Blazor WebAssembly, you can choose to make synchronous JS interop calls. This has slightly less overhead than making asynchronous calls and can result in fewer render cycles because there’s no intermediate state while awaiting results.

To make a synchronous call from JavaScript to .NET in Blazor WebAssembly apps, use DotNet.invokeMethod instead of DotNet.invokeMethodAsync.

Synchronous calls work if:

  • The app is running on Blazor WebAssembly, not Blazor Server.
  • The called function returns a value synchronously. The function isn’t an async method and doesn’t return a .NET Task or JavaScript Promise.

Location of JavaScript

Load JavaScript (JS) code using any of approaches described by the JS interop overview article:

For information on isolating scripts in JS modules, see the JavaScript isolation in JavaScript modules section.

Warning

Don’t place a <script> tag in a component file (.razor) because the <script> tag can’t be updated dynamically.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

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.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Avoid circular object references

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.

Byte array support

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 .NET.

Provide a sendByteArray JS function. The function is called by a button in the component and doesn’t return a value:

<script>
  window.sendByteArray = () => 
    const data = new Uint8Array([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]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => 
        alert(str);
      );
  ;
</script>

Pages/CallDotNetExample8.razor:

@page "/call-dotnet-example-8"
@using System.Text

<h1>Call .NET Example 8</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
    Quote &copy;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 
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    

For information on using a byte array when calling JavaScript from .NET, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Stream from JavaScript to .NET

Blazor supports streaming data directly from JavaScript to .NET. Streams are requested using the Microsoft.JSInterop.IJSStreamReference interface.

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync returns a Stream and uses the following parameters:

  • maxAllowedSize: Maximum number of bytes permitted for the read operation from JavaScript, which defaults to 512,000 bytes if not specified.
  • cancellationToken: A CancellationToken for cancelling the read.

In JavaScript:

function streamToDotNet() 
  return new Uint8Array(10000000);

In C# code:

var dataReference = 
    await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream = 
    await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

In the preceding example:

  • JS is an injected IJSRuntime instance. IJSRuntime is registered by the Blazor framework.
  • The dataReferenceStream is written to disk (file.txt) at the current user’s temporary folder path (GetTempPath).

Call JavaScript functions from .NET methods in ASP.NET Core Blazor covers the reverse operation, streaming from .NET to JavaScript using a DotNetStreamReference.

ASP.NET Core Blazor file uploads covers how to upload a file in Blazor.

Size limits on JavaScript interop calls

This section only applies to Blazor Server apps. In Blazor WebAssembly, the framework doesn’t impose a limit on the size of JavaScript (JS) interop inputs and outputs.

In Blazor Server, JS interop calls 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.

When SignalR logging isn’t set to Debug or Trace, a message size error only appears in the browser’s developer tools console:

Error: Connection disconnected with error ‘Error: Server returned an error on close: Connection closed with an error.’.

When SignalR server-side logging is set to Debug or Trace, server-side logging surfaces an InvalidDataException for a message size error.

appsettings.Development.json:


  "DetailedErrors": true,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    
  

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions.

Increase the limit by setting MaximumReceiveMessageSize in Program.cs. The following example sets the maximum receive message size to 64 KB (64 * 1024):

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Increasing the SignalR incoming message size limit comes at the cost of requiring more server resources, and it exposes the server to increased risks from a malicious user. Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties.

Consider the following guidance when developing code that transfers a large amount of data between JS and Blazor in Blazor Server apps:

  • Leverage the native streaming interop support to transfer data larger than the SignalR incoming message size limit:
  • General tips:
    • Don’t allocate large objects in JS and C# code.
    • Free consumed memory when the process is completed or cancelled.
    • Enforce the following additional requirements for security purposes:
      • Declare the maximum file or data size that can be passed.
      • Declare the minimum upload rate from the client to the server.
    • After the data is received by the server, the data can be:
      • Temporarily stored in a memory buffer until all of the segments are collected.
      • Consumed immediately. For example, the data can be stored immediately in a database or written to disk as each segment is received.

Additional resources

By AKDSEO