Interoperabilidad [JSImport]/[JSExport] de JavaScript con ASP.NET Core Blazor

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

En este artículo se explica cómo interactuar con JavaScript (JS) en componentes del lado cliente mediante JavaScript (JS) [JSImport]/[JSExport] API de interoperabilidad publicada para aplicaciones que adoptan .NET 7 o posterior.

Blazor proporciona su propio mecanismo de interoperabilidad de JS basado en la interfaz de IJSRuntime. La interoperabilidad JS de Blazor se admite uniformemente en los modos de representación de Blazor y en las aplicaciones de Blazor Hybrid. IJSRuntime también permite a los autores de bibliotecas crear bibliotecas de interoperabilidad de JS que se pueden compartir entre el ecosistema de Blazor y sigue siendo el enfoque recomendado para la interoperabilidad de JS en Blazor. Vea los artículos siguientes:

En este artículo se describe una alternativa JS enfoque de interoperabilidad específico de los componentes del lado cliente ejecutados en WebAssembly. Estos enfoques son adecuados cuando solo se espera que se ejecute en WebAssembly del lado cliente. Los autores de bibliotecas pueden usar estos enfoques para optimizar la interoperabilidad de JS comprobando durante la ejecución de código si la aplicación se ejecuta en WebAssembly en un explorador (OperatingSystem.IsBrowser). Los enfoques descritos en este artículo se deben usar para reemplazar la API de interoperabilidad JS obsoleta deserializada al migrar a .NET 7 o versiones posteriores.

Nota:

Este artículo se centra en JS interoperabilidad en componentes del lado cliente. Para obtener instrucciones sobre cómo llamar a .NET en aplicaciones de JavaScript, consulte Ejecución de .NET desde JavaScript.

API de interoperabilidad de JavaScript obsoleta

La interoperabilidad JS deserializada mediante la API IJSUnmarshalledRuntime está obsoleta en ASP.NET Core  en .NET 7 o versiones posteriores. Siga las instrucciones de este artículo para reemplazar la API obsoleta.

Requisitos previos

Descarga e instala .NET 7 o posterior si aún no está instalado en el sistema o si el sistema no tiene instalada la versión más reciente.

Espacio de nombres

La API de interoperabilidad JS descrita en este artículo se controla mediante atributos del espacio de nombres System.Runtime.InteropServices.JavaScript.

Habilitación de bloques no seguros

Habilite la propiedad AllowUnsafeBlocks en el archivo de proyecto de la aplicación, que permite que el generador de código del compilador de Roslyn use punteros para la interoperabilidad JS:

<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Advertencia

La API de interoperabilidad JS requiere habilitar AllowUnsafeBlocks. Tenga cuidado al implementar su propio código no seguro en aplicaciones .NET, pues puede introducir riesgos de seguridad y estabilidad. Para obtener más información, vea Código no seguro, tipos de puntero y punteros de función.

Llamada a JavaScript desde .NET

En esta sección se explica cómo llamar a funciones JS desde .NET.

En el componente CallJavaScript1 siguiente:

  • El módulo CallJavaScript1 se importa de forma asincrónica desde el archivo JS combinado con JSHost.ImportAsync.
  • Se llama a la función importada getMessageJS mediante GetWelcomeMessage.
  • La cadena de mensaje de bienvenida devuelta se muestra en la interfaz de usuario a través del campo message.

CallJavaScript1.razor:

@page "/call-javascript-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override async Task OnInitializedAsync()
    {
        await JSHost.ImportAsync("CallJavaScript1", 
            "../Components/Pages/CallJavaScript1.razor.js");

        message = GetWelcomeMessage();
    }
}
@page "/call-javascript-1"
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override async Task OnInitializedAsync()
    {
        await JSHost.ImportAsync("CallJavaScript1", 
            "../Pages/CallJavaScript1.razor.js");

        message = GetWelcomeMessage();
    }
}

Nota:

Incluya un código de comprobación condicional con OperatingSystem.IsBrowser para asegurarse de que solo un componente representado en el cliente llama a la JS interoperabilidad. Esto es importante para las bibliotecas o paquetes NuGet que tienen como destino los componentes del lado servidor, que no pueden ejecutar el código proporcionado por esta API de interoperabilidad de JS.

Para importar una función JS para llamarla desde C#, use el atributo [JSImport]en una firma de método de C# que coincida con la firma de la función JS. El primer parámetro para el atributo [JSImport] es el nombre de la función JS que se va a importar, y el segundo parámetro es el nombre del módulo JS.

En el ejemplo siguiente, getMessage es una función JS que devuelve un string para un módulo denominado CallJavaScript1. La firma del método de C# coincide: no se pasan parámetros a la función JS y la función JS devuelve un valor string. GetWelcomeMessage llama a la función JS en código de C#.

CallJavaScript1.razor.cs:

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Components.Pages;

[SupportedOSPlatform("browser")]
public partial class CallJavaScript1
{
    [JSImport("getMessage", "CallJavaScript1")]
    internal static partial string GetWelcomeMessage();
}

El espacio de nombres de la aplicación para la clase parcial anterior CallJavaScript1 es BlazorSample. El espacio de nombres del componente es BlazorSample.Components.Pages. Si usa el componente anterior en una aplicación de prueba local, actualice el espacio de nombres para que coincida con la aplicación. Por ejemplo, el espacio de nombres es ContosoApp.Components.Pages si el espacio de nombres de la aplicación es ContosoApp. Para más información, vea Componentes Razor de ASP.NET Core.

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Pages;

[SupportedOSPlatform("browser")]
public partial class CallJavaScript1
{
    [JSImport("getMessage", "CallJavaScript1")]
    internal static partial string GetWelcomeMessage();
}

El espacio de nombres de la aplicación para la clase parcial anterior CallJavaScript1 es BlazorSample. El espacio de nombres del componente es BlazorSample.Pages. Si usa el componente anterior en una aplicación de prueba local, actualice el espacio de nombres para que coincida con la aplicación. Por ejemplo, el espacio de nombres es ContosoApp.Pages si el espacio de nombres de la aplicación es ContosoApp. Para más información, vea Componentes Razor de ASP.NET Core.

En la firma del método importado, puede usar tipos de .NET para parámetros y valores devueltos, que el tiempo de ejecución serializa automáticamente. Use JSMarshalAsAttribute<T> para controlar cómo se serializarán los parámetros del método importado. Por ejemplo, puede optar por serializar long como System.Runtime.InteropServices.JavaScript.JSType.Number o System.Runtime.InteropServices.JavaScript.JSType.BigInt. Puede pasar devoluciones de llamada Action/Func<TResult> como parámetros, que se serializarán como funciones JS invocables. Puede pasar JS y las referencias de objetos administrados, y se serializarán como objetos proxy, manteniendo el objeto activo a través del límite hasta que se recopile el proxy como elemento no utilizado. También puede importar y exportar métodos asincrónicos con un resultado Task, que se serializa como promesas JS. La mayoría de los tipos serializados funcionan en ambas direcciones, como parámetros y como valores devueltos, en métodos importados y exportados, que se tratan en la sección Llamada a .NET desde JavaScript más adelante en este artículo.

En la tabla siguiente se indican las asignaciones de tipos admitidas.

.NET JavaScript Nullable TaskaPromise JSMarshalAs opcional Array of
Boolean Boolean Compatible Compatible Compatible No compatible
Byte Number Compatible Compatible Compatible Compatible
Char String Compatible Compatible Compatible No compatible
Int16 Number Compatible Compatible Compatible No compatible
Int32 Number Compatible Compatible Compatible Compatible
Int64 Number Compatible Compatible No compatible No compatible
Int64 BigInt Compatible Compatible No compatible No compatible
Single Number Compatible Compatible Compatible No compatible
Double Number Compatible Compatible Compatible Compatible
IntPtr Number Compatible Compatible Compatible No compatible
DateTime Date Compatible Compatible No compatible No compatible
DateTimeOffset Date Compatible Compatible No compatible No compatible
Exception Error No compatible Compatible Compatible No compatible
JSObject Object No compatible Compatible Compatible Compatible
String String No compatible Compatible Compatible Compatible
Object Any No compatible Compatible No compatible Compatible
Span<Byte> MemoryView No compatible No compatible No compatible No compatible
Span<Int32> MemoryView No compatible No compatible No compatible No compatible
Span<Double> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Byte> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Int32> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Double> MemoryView No compatible No compatible No compatible No compatible
Task Promise No compatible No compatible Compatible No compatible
Action Function No compatible No compatible No compatible No compatible
Action<T1> Function No compatible No compatible No compatible No compatible
Action<T1, T2> Function No compatible No compatible No compatible No compatible
Action<T1, T2, T3> Function No compatible No compatible No compatible No compatible
Func<TResult> Function No compatible No compatible No compatible No compatible
Func<T1, TResult> Function No compatible No compatible No compatible No compatible
Func<T1, T2, TResult> Function No compatible No compatible No compatible No compatible
Func<T1, T2, T3, TResult> Function No compatible No compatible No compatible No compatible

Las condiciones siguientes se aplican a la asignación de tipos y a los valores serializados:

  • La columna Array of indica si el tipo de .NET se puede serializar como Array de JS. Ejemplo: C# int[] (Int32) asignado a Array de JS de Numbers.
  • Al pasar un valor JS a C# con un valor del tipo incorrecto, el marco produce una excepción en la mayoría de los casos. El marco de trabajo no realiza la comprobación de tipos en tiempo de compilación en JS.
  • JSObject, Exception, Task y ArraySegment crean GCHandle y un proxy. Puede desencadenar la eliminación en el código de desarrollador o permitir que la recolección de elementos no utilizados de .NET elimine los objetos más adelante. Estos tipos conllevan una sobrecarga de rendimiento significativa.
  • Array: al serializar una matriz se crea una copia de dicha matriz en JS o .NET.
  • MemoryView
    • MemoryView es una clase JS para que el tiempo de ejecución de WebAssembly de .NET serialice Span y ArraySegment.
    • A diferencia de serializar una matriz, serializar Span o ArraySegment no crea una copia de la memoria subyacente.
    • Solo se pueden crear instancias de MemoryView correctamente en el tiempo de ejecución de WebAssembly de .NET. Por lo tanto, no es posible importar una función JS como un método de .NET que tenga un parámetro de Span o ArraySegment.
    • Un elemento MemoryView creado para un elemento Span solo es válido durante la llamada de interoperabilidad. Como Span se asigna en la pila de llamadas, la cual no se conserva después de la llamada de interoperabilidad, no es posible exportar un método de .NET que devuelva un Span.
    • Un elemento MemoryView creado para un elemento ArraySegment sigue disponible después de la llamada de interoperabilidad y es útil para compartir un búfer. La llamada a dispose() en un elemento MemoryView creado para un elemento ArraySegment elimina el proxy y desancla la matriz .NET subyacente. Se recomienda llamar a dispose() en un bloque try-finally para MemoryView.

En la tabla siguiente se indican las asignaciones de tipos admitidas.

.NET JavaScript Nullable TaskaPromise JSMarshalAs opcional Array of
Boolean Boolean Compatible Compatible Compatible No compatible
Byte Number Compatible Compatible Compatible Compatible
Char String Compatible Compatible Compatible No compatible
Int16 Number Compatible Compatible Compatible No compatible
Int32 Number Compatible Compatible Compatible Compatible
Int64 Number Compatible Compatible No compatible No compatible
Int64 BigInt Compatible Compatible No compatible No compatible
Single Number Compatible Compatible Compatible No compatible
Double Number Compatible Compatible Compatible Compatible
IntPtr Number Compatible Compatible Compatible No compatible
DateTime Date Compatible Compatible No compatible No compatible
DateTimeOffset Date Compatible Compatible No compatible No compatible
Exception Error No compatible Compatible Compatible No compatible
JSObject Object No compatible Compatible Compatible Compatible
String String No compatible Compatible Compatible Compatible
Object Any No compatible Compatible No compatible Compatible
Span<Byte> MemoryView No compatible No compatible No compatible No compatible
Span<Int32> MemoryView No compatible No compatible No compatible No compatible
Span<Double> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Byte> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Int32> MemoryView No compatible No compatible No compatible No compatible
ArraySegment<Double> MemoryView No compatible No compatible No compatible No compatible
Task Promise No compatible No compatible Compatible No compatible
Action Function No compatible No compatible No compatible No compatible
Action<T1> Function No compatible No compatible No compatible No compatible
Action<T1, T2> Function No compatible No compatible No compatible No compatible
Action<T1, T2, T3> Function No compatible No compatible No compatible No compatible
Func<TResult> Function No compatible No compatible No compatible No compatible
Func<T1, TResult> Function No compatible No compatible No compatible No compatible
Func<T1, T2, TResult> Function No compatible No compatible No compatible No compatible
Func<T1, T2, T3, TResult> Function No compatible No compatible No compatible No compatible

Las condiciones siguientes se aplican a la asignación de tipos y a los valores serializados:

  • La columna Array of indica si el tipo de .NET se puede serializar como Array de JS. Ejemplo: C# int[] (Int32) asignado a Array de JS de Numbers.
  • Al pasar un valor JS a C# con un valor del tipo incorrecto, el marco produce una excepción en la mayoría de los casos. El marco de trabajo no realiza la comprobación de tipos en tiempo de compilación en JS.
  • JSObject, Exception, Task y ArraySegment crean GCHandle y un proxy. Puede desencadenar la eliminación en el código de desarrollador o permitir que la recolección de elementos no utilizados de .NET elimine los objetos más adelante. Estos tipos conllevan una sobrecarga de rendimiento significativa.
  • Array: al serializar una matriz se crea una copia de dicha matriz en JS o .NET.
  • MemoryView
    • MemoryView es una clase JS para que el tiempo de ejecución de WebAssembly de .NET serialice Span y ArraySegment.
    • A diferencia de serializar una matriz, serializar Span o ArraySegment no crea una copia de la memoria subyacente.
    • Solo se pueden crear instancias de MemoryView correctamente en el tiempo de ejecución de WebAssembly de .NET. Por lo tanto, no es posible importar una función JS como un método de .NET que tenga un parámetro de Span o ArraySegment.
    • Un elemento MemoryView creado para un elemento Span solo es válido durante la llamada de interoperabilidad. Como Span se asigna en la pila de llamadas, la cual no se conserva después de la llamada de interoperabilidad, no es posible exportar un método de .NET que devuelva un Span.
    • Un elemento MemoryView creado para un elemento ArraySegment sigue disponible después de la llamada de interoperabilidad y es útil para compartir un búfer. La llamada a dispose() en un elemento MemoryView creado para un elemento ArraySegment elimina el proxy y desancla la matriz .NET subyacente. Se recomienda llamar a dispose() en un bloque try-finally para MemoryView.

El nombre del módulo en el atributo [JSImport] y la llamada para cargar el módulo en el componente con JSHost.ImportAsync debe coincidir y ser único en la aplicación. Al crear una biblioteca para la implementación en un paquete NuGet, se recomienda usar el espacio de nombres del paquete NuGet como prefijo en los nombres de módulo. En el ejemplo siguiente, el nombre del módulo refleja el paquete Contoso.InteropServices.JavaScript y una carpeta de clases de interoperabilidad de mensajes de usuario (UserMessages):

[JSImport("getMessage", 
    "Contoso.InteropServices.JavaScript.UserMessages.CallJavaScript1")]

Las funciones accesibles en el espacio de nombres global se pueden importar mediante el prefijo globalThis en el nombre de la función y mediante el uso del atributo [JSImport] sin proporcionar un nombre de módulo. En el ejemplo siguiente, console.log tiene el prefijo globalThis. El método Log de C# llama a la función importada y acepta un mensaje de cadena de C# (message) y serializa la cadena de C# en un JSString para console.log:

[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);

Exporte scripts desde un módulo de JavaScript ES6 estándar, ya sea combinados con un componente o colocados con otros recursos estáticos de JavaScript en un archivo JS (por ejemplo, wwwroot/js/{FILE NAME}.js, donde los recursos estáticos de JS se mantienen en una carpeta denominada js en la carpeta wwwroot de la aplicación y el marcador de posición {FILE NAME} es el nombre de archivo).

En el ejemplo siguiente, se exporta una función JS denominada getMessage desde un archivo combinado JS que devuelve un mensaje de bienvenida "¡Hola desde Blazor!" en portugués:

CallJavaScript1.razor.js:

export function getMessage() {
  return 'Olá do Blazor!';
}

Llamada a .NET desde JavaScript

En esta sección se explica cómo llamar a métodos de .NET desde JS.

El siguiente componente CallDotNet1 llama a JS,que interactúa directamente con DOM para representar la cadena de mensaje de bienvenida:

  • El CallDotNetmódulo JS se importa de forma asincrónica desde el archivo JS combinado para este componente.
  • Se llama a la función importada setMessageJS mediante SetWelcomeMessage.
  • El mensaje de bienvenida devuelto se muestra mediante setMessage en la interfaz de usuario a través del campo message.

Importante

En el ejemplo de esta sección, la interoperabilidad JS se usa para mutar un elemento DOM exclusivamente con fines de demostración después de que el componente se represente en OnAfterRender. Normalmente, solo debe mutar el DOM con JS cuando el objeto no interactúa con Blazor. El enfoque que se muestra en esta sección es similar a los casos en los que se usa una biblioteca de terceros JS en un componente Razor, donde el componente interactúa con la biblioteca JS a través de la interoperabilidad JS, la biblioteca de terceros JS interactúa con parte del DOM y Blazor no está implicado directamente con las actualizaciones de DOM de esa parte del DOM. Para más información, vea Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

CallDotNet1.razor:

@page "/call-dotnet-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call .NET Example 1)
</h1>

<p>
    <span id="result">.NET method not executed yet</span>
</p>

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSHost.ImportAsync("CallDotNet1", 
                "../Components/Pages/CallDotNet1.razor.js");

            SetWelcomeMessage();
        }
    }
}
@page "/call-dotnet-1"
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call .NET Example 1)
</h1>

<p>
    <span id="result">.NET method not executed yet</span>
</p>

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSHost.ImportAsync("CallDotNet1", 
                "../Pages/CallDotNet1.razor.js");

            SetWelcomeMessage();
        }
    }
}

Para exportar un método de .NET para que se le pueda llamar desde JS, use el atributo [JSExport].

En el ejemplo siguiente:

  • SetWelcomeMessage llama a una función JS denominada setMessage. La función JS llama a .NET para recibir el mensaje de bienvenida de GetMessageFromDotnet y muestra el mensaje en la interfaz de usuario.
  • GetMessageFromDotnet es un método .NET con el atributo [JSExport] que devuelve un mensaje de bienvenida, "¡Hello from Blazor!" en portugués.

CallDotNet1.razor.cs:

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Components.Pages;

[SupportedOSPlatform("browser")]
public partial class CallDotNet1
{
    [JSImport("setMessage", "CallDotNet1")]
    internal static partial void SetWelcomeMessage();

    [JSExport]
    internal static string GetMessageFromDotnet()
    {
        return "Olá do Blazor!";
    }
}

El espacio de nombres de la aplicación para la clase parcial anterior CallDotNet1 es BlazorSample. El espacio de nombres del componente es BlazorSample.Components.Pages. Si usa el componente anterior en una aplicación de prueba local, actualice el espacio de nombres de la aplicación para que coincida con la aplicación. Por ejemplo, el espacio de nombres del componente es ContosoApp.Components.Pages si el espacio de nombres de la aplicación es ContosoApp. Para más información, vea Componentes Razor de ASP.NET Core.

En el ejemplo siguiente, se importa una función JS denominada setMessage desde un archivo combinado JS.

El método setMessage realiza las acciones siguientes:

  • Llama a globalThis.getDotnetRuntime(0) para exponer la instancia del entorno de ejecución de .NET de WebAssembly para llamar a métodos .NET exportados.
  • Obtiene las exportaciones JS del ensamblado de la aplicación. En el ejemplo siguiente, el nombre del ensamblado de la aplicación es BlazorSample.
  • Llama al método BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet desde las exportaciones (exports). El valor devuelto, que es el mensaje de bienvenida, se asigna al texto <span> del componente CallDotNet1. El espacio de nombres de la aplicación es BlazorSample y el espacio de nombres del componente CallDotNet1 es BlazorSample.Components.Pages.

CallDotNet1.razor.js:

export async function setMessage() {
  const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
  var exports = await getAssemblyExports("BlazorSample.dll");

  document.getElementById("result").innerText = 
    exports.BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet();
}
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Pages;

[SupportedOSPlatform("browser")]
public partial class CallDotNet1
{
    [JSImport("setMessage", "CallDotNet1")]
    internal static partial void SetWelcomeMessage();

    [JSExport]
    internal static string GetMessageFromDotnet()
    {
        return "Olá do Blazor!";
    }
}

El espacio de nombres de la aplicación para la clase parcial anterior CallDotNet1 es BlazorSample. El espacio de nombres del componente es BlazorSample.Pages. Si usa el componente anterior en una aplicación de prueba local, actualice el espacio de nombres de la aplicación para que coincida con la aplicación. Por ejemplo, el espacio de nombres del componente es ContosoApp.Pages si el espacio de nombres de la aplicación es ContosoApp. Para más información, vea Componentes Razor de ASP.NET Core.

En el ejemplo siguiente, se importa una función JS denominada setMessage desde un archivo combinado JS.

El método setMessage realiza las acciones siguientes:

  • Llama a globalThis.getDotnetRuntime(0) para exponer la instancia del entorno de ejecución de .NET de WebAssembly para llamar a métodos .NET exportados.
  • Obtiene las exportaciones JS del ensamblado de la aplicación. En el ejemplo siguiente, el nombre del ensamblado de la aplicación es BlazorSample.
  • Llama al método BlazorSample.Pages.CallDotNet1.GetMessageFromDotnet desde las exportaciones (exports). El valor devuelto, que es el mensaje de bienvenida, se asigna al texto <span> del componente CallDotNet1. El espacio de nombres de la aplicación es BlazorSample y el espacio de nombres del componente CallDotNet1 es BlazorSample.Pages.

CallDotNet1.razor.js:

export async function setMessage() {
  const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
  var exports = await getAssemblyExports("BlazorSample.dll");

  document.getElementById("result").innerText = 
    exports.BlazorSample.Pages.CallDotNet1.GetMessageFromDotnet();
}

Nota:

La llamada a getAssemblyExports para obtener las exportaciones puede producirse en un inicializador de JavaScript para la disponibilidad en toda la aplicación.

Varias llamadas de importación de módulos

Una vez cargado un módulo JS, las funciones del módulo JS están disponibles para los componentes y clases de la aplicación, siempre y cuando la aplicación se ejecute en la ventana del explorador o en la pestaña sin que el usuario vuelva a cargar manualmente la aplicación. Se puede llamar varias veces a JSHost.ImportAsync en el mismo módulo sin una penalización de rendimiento significativa cuando:

  • El usuario visita un componente que llama a JSHost.ImportAsync para importar un módulo, sale del componente y, a continuación, vuelve al componente donde se llama a JSHost.ImportAsync de nuevo para la misma importación del módulo.
  • El mismo módulo lo usan los distintos componentes y JSHost.ImportAsync los carga en cada uno de los componentes.

Uso de un único módulo de JavaScript entre componentes

Antes de seguir las instrucciones de esta sección, lea las secciones Llamada a JavaScript desde .NET y Llamada a .NET desde JavaScript de este artículo, que proporcionan instrucciones generales sobre [JSImport] la interoperabilidad /[JSExport].

En el ejemplo de esta sección se muestra cómo usar JS interoperabilidad desde un módulo de JS compartido en una aplicación del lado cliente. Las instrucciones de esta sección no se aplican a las bibliotecas de clases Razor (RCL).

Se usan los siguientes componentes, clases, métodos de C# y funciones JS:

  • Clase Interop (Interop.cs): configura la interoperabilidad de importación y exportación JS con los atributos [JSImport] y [JSExport] de un módulo denominado Interop.
    • GetWelcomeMessage: método .NET que llama a la función importada getMessageJS.
    • SetWelcomeMessage: método .NET que llama a la función importada setMessageJS.
    • GetMessageFromDotnet: método de C# exportado que devuelve una cadena de mensaje de bienvenida cuando se le llama desde JS.
  • Archivo wwwroot/js/interop.js: contiene las funciones JS.
    • getMessage: devuelve un mensaje de bienvenida cuando lo llama el código de C# en un componente.
    • setMessage: llama al método de C# GetMessageFromDotnet y asigna el mensaje de bienvenida devuelto a un elemento DOM <span>.
  • Program.cs llama a JSHost.ImportAsync para cargar el módulo desde wwwroot/js/interop.js.
  • Componente CallJavaScript2 (CallJavaScript2.razor): llama a GetWelcomeMessage y muestra el mensaje de bienvenida devuelto en la interfaz de usuario del componente.
  • Componente CallDotNet2 (CallDotNet2.razor): llama a SetWelcomeMessage.

Interop.cs:

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.JavaScriptInterop;

[SupportedOSPlatform("browser")]
public partial class Interop
{
    [JSImport("getMessage", "Interop")]
    internal static partial string GetWelcomeMessage();

    [JSImport("setMessage", "Interop")]
    internal static partial void SetWelcomeMessage();

    [JSExport]
    internal static string GetMessageFromDotnet()
    {
        return "Olá do Blazor!";
    }
}

En el ejemplo anterior, el espacio de nombres de la aplicación es BlazorSample y el espacio de nombres completo para las clases de interoperabilidad de C# es BlazorSample.JavaScriptInterop.

wwwroot/js/interop.js:

export function getMessage() {
  return 'Olá do Blazor!';
}

export async function setMessage() {
  const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
  var exports = await getAssemblyExports("BlazorSample.dll");

  document.getElementById("result").innerText =
    exports.BlazorSample.JavaScriptInterop.Interop.GetMessageFromDotnet();
}

Haga que el espacio de nombres System.Runtime.InteropServices.JavaScript esté disponible en la parte superior del archivo Program.cs:

using System.Runtime.InteropServices.JavaScript;

Cargue el módulo en Program.cs antes de llamar a WebAssemblyHost.RunAsync:

if (OperatingSystem.IsBrowser())
{
    await JSHost.ImportAsync("Interop", "../js/interop.js");
}

CallJavaScript2.razor:

@page "/call-javascript-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call JS Example 2)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = Interop.GetWelcomeMessage();
    }
}
@page "/call-javascript-2"
@using BlazorSample.JavaScriptInterop

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop 
    (Call JS Example 2)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = Interop.GetWelcomeMessage();
    }
}

CallDotNet2.razor:

@page "/call-dotnet-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop  
    (Call .NET Example 2)
</h1>

<p>
    <span id="result">.NET method not executed</span>
</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Interop.SetWelcomeMessage();
        }
    }
}
@page "/call-dotnet-2"
@using BlazorSample.JavaScriptInterop

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop  
    (Call .NET Example 2)
</h1>

<p>
    <span id="result">.NET method not executed</span>
</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Interop.SetWelcomeMessage();
        }
    }
}

Importante

En el ejemplo de esta sección, la interoperabilidad JS se usa para mutar un elemento DOM exclusivamente con fines de demostración después de que el componente se represente en OnAfterRender. Normalmente, solo debe mutar el DOM con JS cuando el objeto no interactúa con Blazor. El enfoque que se muestra en esta sección es similar a los casos en los que se usa una biblioteca de terceros JS en un componente Razor, donde el componente interactúa con la biblioteca JS a través de la interoperabilidad JS, la biblioteca de terceros JS interactúa con parte del DOM y Blazor no está implicado directamente con las actualizaciones de DOM de esa parte del DOM. Para más información, vea Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

Recursos adicionales