AspNetCore.Docs/aspnetcore/blazor/call-dotnet-from-javascript.md

14 KiB

title author description monikerRange ms.author ms.custom ms.date no-loc uid
Call .NET methods from JavaScript functions in ASP.NET Core Blazor guardrex Learn how to invoke .NET methods from JavaScript functions in Blazor apps. >= aspnetcore-3.1 riande mvc 08/12/2020
ASP.NET Core Identity
cookie
Cookie
Blazor
Blazor Server
Blazor WebAssembly
Identity
Let's Encrypt
Razor
SignalR
blazor/call-dotnet-from-javascript

Call .NET methods from JavaScript functions in ASP.NET Core Blazor

By Javier Calvarro Nelson, Daniel Roth, Shashikant Rudrawadi, and Luke Latham

A Blazor app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions. These scenarios are called JavaScript interoperability (JS interop).

This article covers invoking .NET methods from JavaScript. For information on how to call JavaScript functions from .NET, see xref:blazor/call-javascript-from-dotnet.

View or download sample code (how to download)

Static .NET method call

To invoke a static .NET method from JavaScript, use the DotNet.invokeMethod or DotNet.invokeMethodAsync functions. Pass in the identifier of the static method you wish to call, the name of the assembly containing the function, and any arguments. The asynchronous version is preferred to support Blazor Server scenarios. The .NET method must be public, static, and have the [JSInvokable] attribute. Calling open generic methods isn't currently supported.

The sample app includes a C# method to return an int array. The [JSInvokable] attribute is applied to the method.

Pages/JsInterop.razor:

<button type="button" class="btn btn-primary"
        onclick="exampleJsFunctions.returnArrayAsyncJs()">
    Trigger .NET static method ReturnArrayAsync
</button>

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

JavaScript served to the client invokes the C# .NET method.

wwwroot/exampleJsInterop.js:

[!code-javascript]

When the Trigger .NET static method ReturnArrayAsync button is selected, examine the console output in the browser's web developer tools.

The console output is:

Array(4) [ 1, 2, 3, 4 ]

The fourth array value is pushed to the array (data.push(4);) returned by ReturnArrayAsync.

By default, the method identifier is the method name, but you can specify a different identifier using the [JSInvokable] attribute constructor:

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

In the client-side JavaScript file:

returnArrayAsyncJs: function () {
  DotNet.invokeMethodAsync('{APP ASSEMBLY}', 'DifferentMethodName')
    .then(data => {
      data.push(4);
      console.log(data);
    });
}

The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Instance method call

You can also call .NET instance methods from JavaScript. To invoke a .NET instance method from JavaScript:

[!NOTE] The sample app logs messages to the client-side console. For the following examples demonstrated by the sample app, examine the browser's console output in the browser's developer tools.

When the Trigger .NET instance method HelloHelper.SayHello button is selected, ExampleJsInterop.CallHelloHelperSayHello is called and passes a name, Blazor, to the method.

Pages/JsInterop.razor:

<button type="button" class="btn btn-primary" @onclick="TriggerNetInstanceMethod">
    Trigger .NET instance method HelloHelper.SayHello
</button>

@code {
    public async Task TriggerNetInstanceMethod()
    {
        var exampleJsInterop = new ExampleJsInterop(JSRuntime);
        await exampleJsInterop.CallHelloHelperSayHello("Blazor");
    }
}

CallHelloHelperSayHello invokes the JavaScript function sayHello with a new instance of HelloHelper.

JsInteropClasses/ExampleJsInterop.cs:

[!code-csharp]

wwwroot/exampleJsInterop.js:

[!code-javascript]

The name is passed to HelloHelper's constructor, which sets the HelloHelper.Name property. When the JavaScript function sayHello is executed, HelloHelper.SayHello returns the Hello, {Name}! message, which is written to the console by the JavaScript function.

JsInteropClasses/HelloHelper.cs:

[!code-csharp]

Console output in the browser's web developer tools:

Hello, Blazor!

To avoid a memory leak and allow garbage collection on a component that creates a xref:Microsoft.JSInterop.DotNetObjectReference, adopt one of the following approaches:

  • Dispose of the object in the class that created the xref:Microsoft.JSInterop.DotNetObjectReference instance:

    public class ExampleJsInterop : IDisposable
    {
        private readonly IJSRuntime jsRuntime;
        private DotNetObjectReference<HelloHelper> objRef;
    
        public ExampleJsInterop(IJSRuntime jsRuntime)
        {
            this.jsRuntime = jsRuntime;
        }
    
        public ValueTask<string> CallHelloHelperSayHello(string name)
        {
            objRef = DotNetObjectReference.Create(new HelloHelper(name));
    
            return jsRuntime.InvokeAsync<string>(
                "exampleJsFunctions.sayHello",
                objRef);
        }
    
        public void Dispose()
        {
            objRef?.Dispose();
        }
    }
    

    The preceding pattern shown in the ExampleJsInterop class can also be implemented in a component:

    @page "/JSInteropComponent"
    @using {APP ASSEMBLY}.JsInteropClasses
    @implements IDisposable
    @inject IJSRuntime JSRuntime
    
    <h1>JavaScript Interop</h1>
    
    <button type="button" class="btn btn-primary" @onclick="TriggerNetInstanceMethod">
        Trigger .NET instance method HelloHelper.SayHello
    </button>
    
    @code {
        private DotNetObjectReference<HelloHelper> objRef;
    
        public async Task TriggerNetInstanceMethod()
        {
            objRef = DotNetObjectReference.Create(new HelloHelper("Blazor"));
    
            await JSRuntime.InvokeAsync<string>(
                "exampleJsFunctions.sayHello",
                objRef);
        }
    
        public void Dispose()
        {
            objRef?.Dispose();
        }
    }
    

    The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

  • When the component or class doesn't dispose of the xref:Microsoft.JSInterop.DotNetObjectReference, dispose of the object on the client by calling .dispose():

    window.myFunction = (dotnetHelper) => {
      dotnetHelper.invokeMethodAsync('{APP ASSEMBLY}', 'MyMethod');
      dotnetHelper.dispose();
    }
    

Component instance method call

To invoke a component's .NET methods:

  • Use the invokeMethod or invokeMethodAsync function to make a static method call to the component.
  • The component's static method wraps the call to its instance method as an invoked xref:System.Action.

[!NOTE] For Blazor Server apps, where several users might be concurrently using the same component, use a helper class to invoke instance methods.

For more information, see the Component instance method helper class section.

In the client-side JavaScript:

function updateMessageCallerJS() {
  DotNet.invokeMethodAsync('{APP ASSEMBLY}', 'UpdateMessageCaller');
}

The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Pages/JSInteropComponent.razor:

@page "/JSInteropComponent"

<p>
    Message: @message
</p>

<p>
    <button onclick="updateMessageCallerJS()">Call JS Method</button>
</p>

@code {
    private static Action action;
    private string message = "Select the button.";

    protected override void OnInitialized()
    {
        action = UpdateMessage;
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        StateHasChanged();
    }

    [JSInvokable]
    public static void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

To pass arguments to the instance method:

  • Add parameters to the JS method invocation. In the following example, a name is passed to the method. Additional parameters can be added to the list as needed.

    function updateMessageCallerJS(name) {
      DotNet.invokeMethodAsync('{APP ASSEMBLY}', 'UpdateMessageCaller', name);
    }
    

    The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

  • Provide the correct types to the xref:System.Action for the parameters. Provide the parameter list to the C# methods. Invoke the xref:System.Action (UpdateMessage) with the parameters (action.Invoke(name)).

    Pages/JSInteropComponent.razor:

    @page "/JSInteropComponent"
    
    <p>
        Message: @message
    </p>
    
    <p>
        <button onclick="updateMessageCallerJS('Sarah Jane')">
            Call JS Method
        </button>
    </p>
    
    @code {
        private static Action<string> action;
        private string message = "Select the button.";
    
        protected override void OnInitialized()
        {
            action = UpdateMessage;
        }
    
        private void UpdateMessage(string name)
        {
            message = $"{name}, UpdateMessage Called!";
            StateHasChanged();
        }
    
        [JSInvokable]
        public static void UpdateMessageCaller(string name)
        {
            action.Invoke(name);
        }
    }
    

    Output message when the Call JS Method button is selected:

    Sarah Jane, UpdateMessage Called!
    

Component instance method helper class

The helper class is used to invoke an instance method as an xref:System.Action. Helper classes are useful when:

  • Several components of the same type are rendered on the same page.
  • A Blazor Server app is used, where multiple users might be using a component concurrently.

In the following example:

  • The JSInteropExample component contains several ListItem components.
  • 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.

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

    [JSInvokable("{APP ASSEMBLY}")]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

In the client-side JavaScript:

window.updateMessageCallerJS = (dotnetHelper) => {
    dotnetHelper.invokeMethodAsync('{APP ASSEMBLY}', 'UpdateMessageCaller');
    dotnetHelper.dispose();
}

The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Shared/ListItem.razor:

@inject IJSRuntime JsRuntime

<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 JsRuntime.InvokeVoidAsync("updateMessageCallerJS",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    }

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

Pages/JSInteropExample.razor:

@page "/JSInteropExample"

<h1>List of components</h1>

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

[!INCLUDEShare interop code in a class library]

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.

For more information, see the following issues:

Additional resources