Ejecución de .NET desde JavaScript
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.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la Directiva de soporte técnico de .NET y .NET Core. 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 ejecutar .NET desde JavaScript (JS) mediante la interoperabilidad JS[JSImport]
/[JSExport]
.
Para obtener instrucciones adicionales, consulte la guía Configuración y hospedaje de aplicaciones WebAssembly de .NET en el repositorio de GitHub (dotnet/runtime
) en tiempo de ejecución 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, consulte 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
Instale la última versión del SDK de .NET.
Instale 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. Seleccione la opción herramientas de compilación WebAssembly de .NET de la lista de componentes opcionales.
Opcionalmente, instale 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 tiene previsto integrar la interoperabilidad JS[JSImport]
/[JSExport]
en una aplicación existente deJS.
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, consulte 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:
Establezca 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.Habilite 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. 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, consulte: 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>
Establezca el moniker de la plataforma de destino:
<TargetFramework>net7.0</TargetFramework>
Se admite .NET 7 (
net7.0
) o posterior.Especifique
browser-wasm
para el identificador en tiempo de ejecución:<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
Especifique un tipo de salida ejecutable:
<OutputType>Exe</OutputType>
Habilite 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. 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, consulte: Código no seguro, tipos de puntero y punteros de función.
Especifique 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 en el disco JS 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, copie 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 el resto de la aplicación. Para las implementaciones de producción, publique la aplicación con el comando
dotnet publish -c Release
en un shell de comandos e implemente 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#, use 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, 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 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.
En la tabla siguiente se indican las asignaciones de tipos admitidas.
.NET | JavaScript | Nullable |
Task a Promise |
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 comoArray
de JS. Ejemplo: C#int[]
(Int32
) asignado aArray
de JS deNumber
s. - 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
yArraySegment
creanGCHandle
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 serialiceSpan
yArraySegment
.- A diferencia de serializar una matriz, serializar
Span
oArraySegment
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 deSpan
oArraySegment
. - Un elemento
MemoryView
creado para un elementoSpan
solo es válido durante la llamada de interoperabilidad. ComoSpan
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 unSpan
. - Un elemento
MemoryView
creado para un elementoArraySegment
sigue disponible después de la llamada de interoperabilidad y es útil para compartir un búfer. La llamada adispose()
en un elementoMemoryView
creado para un elementoArraySegment
elimina el proxy y desancla la matriz .NET subyacente. Se recomienda llamar adispose()
en un bloquetry-finally
paraMemoryView
.
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, use 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, instale 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
Puede 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, puede crear la aplicación mediante la plantilla de proyecto WebAssembly Browser App.
Compile la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet build
Compile y ejecute la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet run
Como alternativa, instale y use 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
Puede 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, puede crear la aplicación mediante la plantilla de proyecto WebAssembly Console App.
Compile la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet build
Compile y ejecute la aplicación desde Visual Studio o mediante la CLI de .NET:
dotnet run
Como alternativa, inicie 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 JSImport/JSExport de JavaScript con ASP.NET Core Blazor
- En el repositorio
dotnet/runtime
de GitHub: - Use .NET desde cualquier aplicación de JavaScript en .NET 7
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente las Cuestiones de GitHub como mecanismo de retroalimentación para el contenido y lo sustituiremos por un nuevo sistema de retroalimentación. Para más información, consulta:Enviar y ver comentarios de