Interoperabilidad [JSImport]
/[JSExport]
de JavaScript
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta 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 de .NET 9 de este artículo.
En este artículo se explica cómo ejecutar .NET desde JavaScript (JS) mediante la interoperabilidad JS[JSImport]
/[JSExport]
.
Para obtener instrucciones adicionales, consulta la guía Configuración y hospedaje de aplicaciones WebAssembly de .NET en el repositorio de GitHub (dotnet/runtime
) en runtime de .NET.
Las aplicacionesJS existentes pueden usar la compatibilidad ampliada con WebAssembly del lado del cliente para reutilizar bibliotecas .NET JS o para crear nuevas aplicaciones y marcos basados en .NET.
Nota:
Este artículo se centra en la ejecución de .NET desde aplicaciones JS sin ninguna dependencia de Blazor. Para obtener instrucciones sobre cómo usar la interoperabilidad [JSImport]
/[JSExport]
en aplicaciones Blazor WebAssembly, consulta Interoperabilidad JSImport/JSExport de JavaScript con ASP.NET Core Blazor.
Estos enfoques son adecuados cuando solo se espera que se ejecute en WebAssembly (WASM). Las bibliotecas pueden realizar una comprobación en tiempo de ejecución para determinar si la aplicación se ejecuta en WASM mediante una llamada a OperatingSystem.IsBrowser.
Requisitos previos
SDK de .NET (versión más reciente)
Instala la carga de trabajo de wasm-tools
en un shell de comandos administrativo, que incluye los destinos de MSBuild relacionados:
dotnet workload install wasm-tools
Las herramientas también se pueden instalar mediante el instalador de Visual Studio en la carga de trabajo ASP.NET y desarrollo web en el instalador de Visual Studio. Selecciona la opción herramientas de compilación WebAssembly de .NET de la lista de componentes opcionales.
Opcionalmente, instala la carga de trabajo wasm-experimental
, que contiene plantillas de proyecto experimentales para empezar a trabajar con .NET en WebAssembly en una aplicación de explorador (aplicación de explorador de WebAssembly) o en una aplicación de consola basada en Node.js (aplicación de consola WebAssembly). Esta carga de trabajo no es necesaria si tienes previsto integrar la interoperabilidad JS[JSImport]
/[JSExport]
en una aplicación JS existente.
dotnet workload install wasm-experimental
Las plantillas también se pueden instalar desde el paquete NuGet de Microsoft.NET.Runtime.WebAssembly.Templates
con el siguiente comando:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
Para obtener más información, consulta la sección: Plantillas de proyecto y carga de trabajo experimentales.
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.
Configuración de proyecto
Para configurar un proyecto (.csproj
) para habilitar la interoperabilidad JS:
Establece el moniker de la plataforma de destino (
{TARGET FRAMEWORK}
marcador de posición):<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
Se admite .NET 7 (
net7.0
) o posterior.Habilita la propiedad AllowUnsafeBlocks, que permite que el generador de código del compilador de Roslyn use punteros para la interoperabilidad JS:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Advertencia
La API de interoperabilidad JS requiere habilitar AllowUnsafeBlocks. Ten cuidado al implementar tu propio código no seguro en aplicaciones .NET, pues puedes introducir riesgos de seguridad y estabilidad. Para obtener más información, consulta: Código no seguro, tipos de puntero y punteros de función.
A continuación se muestra un archivo de proyecto de ejemplo (.csproj
) después de la configuración. El marcador de posición {TARGET FRAMEWORK}
es la plataforma de destino:
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
Establece el moniker de la plataforma de destino:
<TargetFramework>net7.0</TargetFramework>
Se admite .NET 7 (
net7.0
) o posterior.Especifica
browser-wasm
para el identificador en tiempo de ejecución:<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
Especifica un tipo de salida ejecutable:
<OutputType>Exe</OutputType>
Habilita la propiedad AllowUnsafeBlocks, que permite que el generador de código del compilador de Roslyn use punteros para la interoperabilidad JS:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Advertencia
La API de interoperabilidad JS requiere habilitar AllowUnsafeBlocks. Ten cuidado al implementar tu propio código no seguro en aplicaciones .NET, pues puedes introducir riesgos de seguridad y estabilidad. Para obtener más información, consulta: Código no seguro, tipos de puntero y punteros de función.
Especifica a
WasmMainJSPath
para que apunte a un archivo en el disco. Este archivo se publica con la aplicación, pero el uso del archivo no es necesario si va a integrar .NET en una aplicación JS existente.En el ejemplo siguiente, el archivo JS en el disco es
main.js
, pero se permite cualquier nombre de archivo JS:<WasmMainJSPath>main.js</WasmMainJSPath>
Archivo de proyecto de ejemplo (.csproj
) después de la configuración:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WasmMainJSPath>main.js</WasmMainJSPath>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Interoperabilidad de JavaScript en WASM
Las API del ejemplo siguiente se importan desde dotnet.js
. Estas API permiten configurar módulos con nombre que se pueden importar en el código de C# y llamar a métodos expuestos por el código de .NET, incluido Program.Main
.
Importante
Los términos "importar" y "exportar" se definen en este artículo desde la perspectiva de .NET:
- Una aplicación importa métodos JS para que se puedan llamar desde .NET.
- La aplicación exporta métodos de .NET para que se puedan llamar desde JS.
En el ejemplo siguiente:
El archivo
dotnet.js
se usa para crear e iniciar el tiempo de ejecución de WebAssembly de .NET.dotnet.js
se genera como parte de la salida de compilación de la aplicación.Importante
Para integrar con una aplicación existente, copia el contenido de la carpeta† de salida de publicación en los recursos de implementación de la aplicación existente para que se pueda servir junto con rest de la aplicación. Para las implementaciones de producción, publica la aplicación con el comando
dotnet publish -c Release
en un shell de comandos e implementa el contenido de la carpeta de salida con la aplicación.†La carpeta de salida de publicación es la ubicación de destino del perfil de publicación. El valor predeterminado de un perfil de Release en .NET 8 o posterior es
bin/Release/{TARGET FRAMEWORK}/publish
, donde el marcador de posición de{TARGET FRAMEWORK}
es la plataforma de destino (por ejemplo,net8.0
).dotnet.create()
configura el tiempo de ejecución de WebAssembly de .NET.
setModuleImports
asocia un nombre a un módulo de funciones JS para la importación en .NET. El módulo JS contiene una funcióndom.setInnerText
, que acepta y el selector de elementos y la hora para mostrar la hora actual del cronómetro en la interfaz de usuario. El nombre del módulo puede ser cualquier cadena (no es necesario que sea un nombre de archivo), pero debe coincidir con el nombre usado conJSImportAttribute
(que se explica más adelante en este artículo). La funcióndom.setInnerText
se importa en C# y la llama el métodoSetInnerText
de C#. El métodoSetInnerText
se muestra más adelante en esta sección.exports.StopwatchSample.Reset()
llama a .NET (StopwatchSample.Reset
) desde JS. El métodoReset
C# reinicia el cronómetro si se está ejecutando o lo restablece si no se está ejecutando. El métodoReset
se muestra más adelante en esta sección.exports.StopwatchSample.Toggle()
llama a .NET (StopwatchSample.Toggle
) desde JS. El métodoToggle
C# inicia o detiene el cronómetro en función de si se está ejecutando o no. El métodoToggle
se muestra más adelante en esta sección.runMain()
ejecutaProgram.Main
.
setModuleImports
asocia un nombre a un módulo de funciones JS para la importación en .NET. El módulo JS contiene una funciónwindow.location.href
, la cual devuelve la dirección de página actual (URL). El nombre del módulo puede ser cualquier cadena (no es necesario que sea un nombre de archivo), pero debe coincidir con el nombre usado conJSImportAttribute
(que se explica más adelante en este artículo). La funciónwindow.location.href
se importa en C# y la llama el métodoGetHRef
de C#. El métodoGetHRef
se muestra más adelante en esta sección.exports.MyClass.Greeting()
llama a .NET (MyClass.Greeting
) desde JS. El métodoGreeting
de C# devuelve una cadena que incluye el resultado de llamar a la funciónwindow.location.href
. El métodoGreeting
se muestra más adelante en esta sección.dotnet.run()
ejecutaProgram.Main
.
Módulo JS:
import { dotnet } from './_framework/dotnet.js'
const { setModuleImports, getAssemblyExports, getConfig, runMain } = await dotnet
.withApplicationArguments("start")
.create();
setModuleImports('main.js', {
dom: {
setInnerText: (selector, time) =>
document.querySelector(selector).innerText = time
}
});
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
document.getElementById('reset').addEventListener('click', e => {
exports.StopwatchSample.Reset();
e.preventDefault();
});
const pauseButton = document.getElementById('pause');
pauseButton.addEventListener('click', e => {
const isRunning = exports.StopwatchSample.Toggle();
pauseButton.innerText = isRunning ? 'Pause' : 'Start';
e.preventDefault();
});
await runMain();
import { dotnet } from './_framework/dotnet.js'
const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withDiagnosticTracing(false)
.withApplicationArgumentsFromQuery()
.create();
setModuleImports('main.js', {
window: {
location: {
href: () => globalThis.window.location.href
}
}
});
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);
document.getElementById('out').innerHTML = text;
await dotnet.run();
import { dotnet } from './dotnet.js'
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
const { setModuleImports, getAssemblyExports, getConfig } =
await dotnet.create();
setModuleImports("main.js", {
window: {
location: {
href: () => globalThis.window.location.href
}
}
});
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);
document.getElementById("out").innerHTML = text;
await dotnet.run();
Para importar una función JS para que se pueda llamar desde C#, usa el nuevo JSImportAttribute en una firma de método coincidente. El primer parámetro para el atributo JSImportAttribute es el nombre de la función JS que se va a importar, y el segundo es el nombre módulo.
En el ejemplo siguiente, se llama a la función dom.setInnerText
desde el módulo main.js
cuando se llama al método SetInnerText
:
[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);
En el ejemplo siguiente, se llama a la función window.location.href
desde el módulo main.js
cuando se llama al método GetHRef
:
[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();
En la firma del método importado, puedes usar tipos de .NET para parámetros y valores devueltos, que el tiempo de ejecución serializa automáticamente. Usa JSMarshalAsAttribute<T> para controlar cómo se serializarán los parámetros del método importado. Por ejemplo, puedes optar por serializar long
como System.Runtime.InteropServices.JavaScript.JSType.Number o System.Runtime.InteropServices.JavaScript.JSType.BigInt. Puedes pasar devoluciones de llamada Action/Func<TResult> como parámetros, que se serializarán como funciones JS invocables. Puedes 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 puedes importar y exportar métodos asincrónicos con un resultado Task, que se serializan como promesas JS. La mayoría de los tipos serializados funcionan en ambas direcciones como parámetros y valores devueltos tanto en métodos importados como exportados.
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);
Para exportar un método de .NET y que se le pueda llamar desde JS, usa el JSExportAttribute.
En el ejemplo siguiente, cada método se exporta a JS y se puede llamar desde JS funciones:
- El método
Toggle
inicia o detiene el cronómetro en función de su estado de ejecución. - El método
Reset
reinicia el cronómetro si se está ejecutando o lo restablece si no se está ejecutando. - El método
IsRunning
indica si se está ejecutando el cronómetro.
[JSExport]
internal static bool Toggle()
{
if (stopwatch.IsRunning)
{
stopwatch.Stop();
return false;
}
else
{
stopwatch.Start();
return true;
}
}
[JSExport]
internal static void Reset()
{
if (stopwatch.IsRunning)
stopwatch.Restart();
else
stopwatch.Reset();
Render();
}
[JSExport]
internal static bool IsRunning() => stopwatch.IsRunning;
En el ejemplo siguiente, el método Greeting
devuelve una cadena que incluye el resultado de llamar al método GetHRef
. Como se ha mostrado anteriormente, el método GetHref
de C# llama a JS para la función window.location.href
desde el módulo main.js
. window.location.href
devuelve la dirección de página actual (URL):
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from {GetHRef()}";
Console.WriteLine(text);
return text;
}
Plantillas de proyecto y carga de trabajo experimentales
Para demostrar la funcionalidad de interoperabilidad JS y obtener plantillas de proyecto de interoperabilidad JS, instala la carga de trabajo wasm-experimental
:
dotnet workload install wasm-experimental
La carga de trabajo wasm-experimental
contiene dos plantillas de proyecto: wasmbrowser
y wasmconsole
. Estas plantillas son experimentales en este momento, lo que significa que el flujo de trabajo del desarrollador de las plantillas está evolucionando. Sin embargo, las API de .NET y JS que se usan en las plantillas se admiten en .NET 8 y proporcionan una base para usar .NET en WASM desde JS.
Las plantillas también se pueden instalar desde el paquete NuGet de Microsoft.NET.Runtime.WebAssembly.Templates
con el siguiente comando:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
Aplicación de explorador
Puedes crear una aplicación de explorador con la plantilla de wasmbrowser
desde la línea de comandos, que crea una aplicación web que muestra el uso de .NET y JS juntos en un explorador:
dotnet new wasmbrowser
Como alternativa, en Visual Studio, puedes crear la aplicación mediante la plantilla de proyecto WebAssembly Browser App.
Compila la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet build
Compila y ejecuta la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet run
Como alternativa, instala y usa el dotnet serve
comando:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish
En el ejemplo anterior, el marcador de posición {TARGET FRAMEWORK}
es el moniker de la plataforma de destino.
Aplicación de consola de Node.js
Puedes crear una aplicación de consola con la plantilla wasmconsole
, que crea una aplicación que se ejecuta en WASM como una aplicación de consola de Node.js o V8:
dotnet new wasmconsole
Como alternativa, en Visual Studio, puedes crear la aplicación mediante la plantilla de proyecto WebAssembly Console App.
Compila la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet build
Compila y ejecuta la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet run
Como alternativa, inicia cualquier servidor de archivos estático desde el directorio de salida de publicación que contenga el archivo main.mjs
:
node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs
En el ejemplo anterior, el marcador de posición {TARGET FRAMEWORK}
es el moniker de la plataforma de destino, y el marcador de posición {PATH}
es la ruta de acceso al archivo main.mjs
.
Recursos adicionales
- Configuración y hospedaje de aplicaciones WebAssembly de .NET
- Documentación de la API
- Interoperabilidad de JSImport/JSExport de JavaScript con ASP.NET Core Blazor
- En el repositorio
dotnet/runtime
de GitHub: - Uso de .NET desde cualquier aplicación de JavaScript en .NET 7