AspNetCore.Docs/aspnetcore/razor-components/javascript-interop.md

14 KiB
Raw Blame History

title author description monikerRange ms.author ms.custom ms.date uid
Razor Components JavaScript interop guardrex Learn how to invoke JavaScript functions from .NET and .NET methods from JavaScript in Blazor and Razor Components apps. >= aspnetcore-3.0 riande mvc 01/29/2019 razor-components/javascript-interop

Razor Components JavaScript interop

By Javier Calvarro Nelson, Daniel Roth, and Luke Latham

A Razor Components app can invoke JavaScript functions from .NET and .NET methods from JavaScript code.

Invoke JavaScript functions from .NET methods

There are times when .NET code is required to call a JavaScript function. For example, a JavaScript call can expose browser capabilities or functionality from a JavaScript library to the app.

To call into JavaScript from .NET, use the IJSRuntime abstraction. The InvokeAsync<T> method on IJSRuntime takes an identifier for the JavaScript function you wish to invoke along with any number of JSON-serializable arguments. The function identifier is relative to the global scope (window). If you wish to call window.someScope.someFunction, the identifier is someScope.someFunction. There's no need to register the function before it's called. The return type T must also be JSON serializable.

For server-side ASP.NET Core Razor Components apps:

  • Multiple user requests are processed by the server-side app. Don't call JSRuntime.Current in a component to invoke JavaScript functions.
  • Inject the IJSRuntime abstraction and use the injected object to issue JavaScript interop calls.

The following example is based on TextDecoder, an experimental JavaScript-based decoder. The example demonstrates how to invoke a JavaScript function from a C# method. The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.

Inside the <head> element of wwwroot/index.html, provide a function that uses TextDecoder to decode a passed array:

<script>
  window.ConvertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

JavaScript code, such as the code shown in the preceding example, can also be loaded by a JavaScript file with a reference to the script file in the wwwroot/index.html file.

The following component:

  • Invokes the ConvertArray JavaScript function using JsRuntime when a component button (Convert Array) is selected.
  • After the JavaScript function is called, the passed array is converted into a string. The string is returned to the component for display.
@page "/"
@using Microsoft.JSInterop;
@inject IJSRuntime JsRuntime;

<h1>Call JavaScript Function Example</h1>

<button type="button" class="btn btn-primary" onclick="@ConvertArray">
    Convert Array
</button>

<p class="mt-2" style="font-size:1.6em">
    <span class="badge badge-success">
        @ConvertedText
    </span>
</p>

@functions {
    // Quote (c)2005 Universal Pictures: Serenity
    // https://www.uphe.com/movies/serenity
    // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/

    private MarkupString ConvertedText =
        new MarkupString("Select the <b>Convert Array</b> button.");

    private uint[] QuoteArray = new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    async void ConvertArray()
    {
        var text =
            await JsRuntime.InvokeAsync<string>("ConvertArray", QuoteArray);

        ConvertedText = new MarkupString(text);

        StateHasChanged();
    }
}

For client-side Blazor apps, the IJSRuntime abstraction is accessible from JSRuntime.Current, which refers to the current user's request. Because there's only a single user of a client-side Blazor app, using JSRuntime.Current to invoke a JavaScript function works normally. Only use JSRuntime.Current in client-side Blazor apps.

In the client-side sample app that accompanies this topic, two JavaScript functions are available to the client-side app that interact with the DOM to receive user input and display a welcome message:

  • showPrompt Produces a prompt to accept user input (the user's name) and returns the name to the caller.
  • displayWelcome Assigns a welcome message from the caller to a DOM object with an id of welcome.

wwwroot/exampleJsInterop.js:

[!code-javascript]

Place the <script> tag that references the JavaScript file in the wwwroot/index.html file:

[!code-html]

Don't place a script tag in a component file because the script tag can't be updated dynamically.

.NET methods interop with the JavaScript functions by calling InvokeAsync<T> method on IJSRuntime.

The sample app uses a pair of C# methods, Prompt and Display, to invoke the showPrompt and displayWelcome JavaScript functions:

JsInteropClasses/ExampleJsInterop.cs:

[!code-csharp]

The IJSRuntime abstraction is asynchronous to allow for server-side scenarios. If the app runs client-side and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime and call Invoke<T> instead. We recommend that most JavaScript interop libraries use the async APIs to ensure the libraries are available in all scenarios, client-side or server-side.

The sample app includes a component to demonstrate JS interop. The component:

  • Receives user input via a JS prompt.
  • Returns the text to the component for processing.
  • Calls a second JS function that interacts with the DOM to display a welcome message.

Pages/JSInterop.cshtml:

[!code-cshtml]

  1. When TriggerJsPrompt is executed by selecting the component's Trigger JavaScript Prompt button, the ExampleJsInterop.Prompt method in C# code is called.
  2. The Prompt method executes the JavaScript showPrompt function provided in the wwwroot/exampleJsInterop.js file.
  3. The showPrompt function accepts user input (the user's name), which is HTML-encoded and returned to the Prompt method and ultimately back to the component. The component stores the user's name in a local variable, name.
  4. The string stored in name is incorporated into a welcome message, which is passed to a second C# method, ExampleJsInterop.Display.
  5. Display calls a JavaScript function, displayWelcome, which renders the welcome message into a heading tag.

Capture references to elements

Some JavaScript interop scenarios require references to HTML elements. For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus or play.

You can capture references to HTML elements in a component by adding a ref attribute to the HTML element and then defining a field of type ElementRef whose name matches the value of the ref attribute.

The following example shows capturing a reference to the username input element:

<input ref="username" ... />

@functions {
    ElementRef username;
}

[!NOTE] Do not use captured element references as a way of populating the DOM. Doing so may interfere with the declarative rendering model.

As far as .NET code is concerned, an ElementRef is an opaque handle. The only thing you can do with it is pass it through to JavaScript code via JavaScript interop. When you do so, the JavaScript-side code receives an HTMLElement instance, which it can use with normal DOM APIs.

For example, the following code defines a .NET extension method that enables setting the focus on an element:

mylib.js:

window.myLib = {
  focusElement : function (element) {
    element.focus();
  }
}

ElementRefExtensions.cs:

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

namespace MyLib
{
    public static class MyLibElementRefExtensions
    {
        public static Task Focus(this ElementRef elementRef)
        {
            return JSRuntime.Current.InvokeAsync<object>("myLib.focusElement", elementRef);
        }
    }
}

Now you can focus inputs in any of your components:

@using MyLib

<input ref="username" />
<button onclick="@SetFocus">Set focus</button>

@functions {
    ElementRef username;

    void SetFocus()
    {
        username.Focus();
    }
}

[!IMPORTANT] The username variable is only populated after the component renders and its output includes the <input> element. If you try to pass an unpopulated ElementRef to JavaScript code, the JavaScript code receives null. To manipulate element references after the component has finished rendering (to set the initial focus on an element) use the OnAfterRenderAsync or OnAfterRender component lifecycle methods.

Invoke .NET methods from JavaScript functions

Static .NET method call

To invoke a static .NET method from JavaScript, use the DotNet.invokeMethod or DotNet.invokeMethodAsync functions. Pass in the identifier of the static method you wish to call, the name of the assembly containing the function, and any arguments. Again, the async version is preferred to support server-side scenarios. To be invokable from JavaScript, the .NET method must be public, static, and decorated with [JSInvokable]. By default, the method identifier is the method name, but you can specify a different identifier using the JSInvokableAttribute constructor. Calling open generic methods isn't currently supported.

The sample app includes a C# method to return an array of ints. The method is decorated with the JSInvokable attribute.

Pages/JsInterop.cshtml:

[!code-cshtml]

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:

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

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

Instance method call

You can also call .NET instance methods from JavaScript. To invoke a .NET instance method from JavaScript, first pass the .NET instance to JavaScript by wrapping it in a DotNetObjectRef instance. The .NET instance is passed by reference to JavaScript, and you can invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.

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

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

Pages/JsInterop.cshtml:

[!code-cshtml]

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!

Share interop code in a Razor Component class library

JavaScript interop code can be included in a Razor Component class library (dotnet new blazorlib), which allows you to share the code in a NuGet package.

The Razor Component class library handles embedding JavaScript resources in the built assembly. The JavaScript files are placed in the wwwroot folder, and the tooling takes care of embedding the resources when the library is built.

The built NuGet package is referenced in the project file of the app just as any normal NuGet package is referenced. After the app has been restored, app code can call into JavaScript as if it were C#.