Exécuter .NET à partir de JavaScript
Cet article explique comment exécuter .NET à partir de JavaScript (JS) en utilisant l’interopérabilité JS[JSImport]
/[JSExport]
.
Pour obtenir des conseils supplémentaires, consultez les instructions relatives à la configuration et à l’hébergement d’applications WebAssembly .NET dans le référentiel GitHub .NET Runtime (dotnet/runtime
). Nous prévoyons de mettre à jour cet article pour inclure de nouvelles informations dans les conseils inter-liés à la fin de 2023 ou au début de 2024.
Les applications JS existantes peuvent tirer parti de la prise en charge étendue de WebAssembly côté client dans .NET 7 ou version ultérieure pour réutiliser les bibliothèques .NET à partir de JS, ou pour créer des applications et des frameworks basés sur .NET.
Remarque
Cet article se concentre sur l’exécution de .NET à partir d’applications JS sans aucune dépendance par rapport à Blazor. Pour obtenir des conseils d’aide sur l’utilisation de l’interopérabilité [JSImport]
/[JSExport]
dans les applications Blazor WebAssembly, consultez Interopérabilité JSImport/JSExport JavaScript avec ASP.NET Core Blazor.
Ces approches sont appropriées quand vous comptez uniquement exécuter le code dans un environnement WebAssembly (WASM). Les bibliothèques peuvent effectuer une vérification au moment de l’exécution pour déterminer si l’application s’exécute sur WASM en appelant OperatingSystem.IsBrowser.
Prérequis
Kit de développement logiciel (SDK) .NET 7.0
Installez la dernière version du kit SDK .NET.
Installez la charge de travail wasm-tools
, qui intègre les cibles MSBuild associées.
dotnet workload install wasm-tools
Si vous le souhaitez, installez la charge de travail wasm-experimental
, qui contient des modèles de projet expérimentaux pour bien démarrer avec .NET sur WebAssembly dans une application de navigateur (application de navigateur WebAssembly) ou dans une application console basée sur Node.js (application console WebAssembly). Cette charge de travail n’est pas nécessaire si vous comptez intégrer l’interopérabilité JS[JSImport]
/[JSExport]
dans une application JS existante.
dotnet workload install wasm-experimental
Pour plus d’informations, consultez la section Modèles de charge de travail et de projet expérimentaux.
Espace de noms
L’API d’interopérabilité JS décrite dans cet article est contrôlée par des attributs de l’espace de noms System.Runtime.InteropServices.JavaScript.
Configuration du projet
Pour configurer un projet (.csproj
) afin d’activer l’interopérabilité JS :
Ciblez
net7.0
ou une version ultérieure :<TargetFramework>net7.0</TargetFramework>
Spécifiez
browser-wasm
pour l’identificateur de runtime :<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
Spécifiez un type de sortie d’exécutable :
<OutputType>Exe</OutputType>
Activez la propriété AllowUnsafeBlocks, qui permet au générateur de code du compilateur Roslyn d’utiliser des pointeurs pour l’interopérabilité JS :
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Avertissement
L’API d’interopérabilité JS nécessite l’activation de AllowUnsafeBlocks. Soyez vigilant quand vous implémentez votre propre code non sécurisé dans des applications .NET, car cela peut présenter des risques pour la sécurité et la stabilité. Pour plus d’informations, consultez Code non sécurisé, types de pointeur et pointeurs de fonction.
Spécifiez
WasmMainJSPath
pour pointer vers un fichier sur le disque. Ce fichier est publié avec l’application, mais son utilisation n’est pas nécessaire si vous intégrez .NET à une application JS existante.Dans l’exemple suivant, le fichier JS situé sur le disque est
main.js
, mais tout nom de fichier JS est autorisé :<WasmMainJSPath>main.js</WasmMainJSPath>
Exemple de fichier projet (.csproj
) après la configuration :
<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>
Interopérabilité JavaScript sur WASM
Les API de l’exemple suivant sont importées à partir de dotnet.js
. Ces API vous permettent de configurer des modules nommés qui peuvent être importés dans votre code C#, et d’appeler des méthodes exposées par votre code .NET, notamment Program.Main
.
Important
« Importer » et « exporter » sont définis tout au long de cet article du point de vue de .NET :
- Une application importe des méthodes JS pour qu’elles puissent être appelées à partir de .NET.
- L’application exporte des méthodes .NET pour qu’elles puissent être appelées à partir de JS.
Dans l’exemple suivant :
Le fichier
dotnet.js
permet de créer et de démarrer le runtime .NET WebAssembly.dotnet.js
est généré dans le cadre de la sortie de build de l’application, et se trouve dans le dossierAppBundle
:bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
L’espace réservé
{BUILD CONFIGURATION}
représente la configuration de build (par exempleDebug
,Release
), et l’espace réservé{TARGET FRAMEWORK}
représente le framework cible (par exemplenet7.0
).Important
Pour effectuer l’intégration à une application existante, copiez le contenu du dossier
AppBundle
pour qu’il soit mis à disposition avec le reste de l’application. Pour les déploiements en production, publiez l’application avec la commandedotnet publish -c Release
dans un interpréteur de commandes, puis déployez le dossierAppBundle
avec l’application.dotnet.create()
configure le runtime .NET WebAssembly.setModuleImports
associe un nom à un module de fonctions JS à des fins d’importation dans .NET. Le module JS contient une fonctionwindow.location.href
, qui retourne l’adresse (URL) de la page active. Le nom du module peut être une chaîne (il n’est pas nécessaire qu’il s’agisse d’un nom de fichier), mais il doit correspondre au nom utilisé avecJSImportAttribute
(expliqué plus loin dans cet article). La fonctionwindow.location.href
est importée dans C# et est appelée par la méthode C#GetHRef
. La méthodeGetHRef
est présentée plus loin dans cette section.exports.MyClass.Greeting()
appelle .NET (MyClass.Greeting
) à partir de JS. La méthode C#Greeting
retourne une chaîne qui inclut le résultat de l’appel de la fonctionwindow.location.href
. La méthodeGreeting
est présentée plus loin dans cette section.dotnet.run()
exécuteProgram.Main
.
Module JS :
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();
Pour importer une fonction JS afin qu’elle puisse être appelée à partir du code C#, utilisez le nouveau JSImportAttribute sur une signature de méthode correspondante. Le premier paramètre de JSImportAttribute est le nom de la fonction JS à importer, et le deuxième paramètre est le nom du module.
Dans l’exemple suivant, la fonction window.location.href
est appelée à partir du module main.js
quand la méthode GetHRef
est appelée :
[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();
Dans la signature de méthode importée, vous pouvez utiliser des types .NET pour les paramètres et les valeurs de retour, qui sont marshalés automatiquement par le runtime. Utilisez JSMarshalAsAttribute<T> pour contrôler la façon dont les paramètres de méthode importés sont marshalés. Par exemple, vous pouvez choisir de marshaler long
en tant que System.Runtime.InteropServices.JavaScript.JSType.Number ou System.Runtime.InteropServices.JavaScript.JSType.BigInt. Vous pouvez passer des rappels Action/Func<TResult> en tant que paramètres, qui sont marshalés en tant que fonctions JS pouvant être appelées. Vous pouvez passer à la fois des références d’objets JS et des références d’objets managés. Elles sont marshalées en tant qu’objets proxy, ce qui permet de conserver l’objet actif entre les environnements jusqu’à ce que le proxy soit traité pour un nettoyage de la mémoire. Vous pouvez également importer et exporter des méthodes asynchrones avec un résultat Task, qui sont marshalées en tant que promesses JS. La plupart des types marshalés fonctionnent dans les deux sens, en tant que paramètres et en tant que valeurs de retour, sur les méthodes importées et exportées.
Le tableau suivant indique les mappages de types pris en charge.
.NET | JavaScript | Nullable |
Task àPromise |
JSMarshalAs facultatif |
Array of |
---|---|---|---|---|---|
Boolean |
Boolean |
Pris en charge | Pris en charge | Pris en charge | Non pris en charge |
Byte |
Number |
Pris en charge | Pris en charge | Pris en charge | Pris en charge |
Char |
String |
Pris en charge | Pris en charge | Pris en charge | Non pris en charge |
Int16 |
Number |
Pris en charge | Pris en charge | Pris en charge | Non pris en charge |
Int32 |
Number |
Pris en charge | Pris en charge | Pris en charge | Pris en charge |
Int64 |
Number |
Pris en charge | Pris en charge | Non pris en charge | Non pris en charge |
Int64 |
BigInt |
Pris en charge | Pris en charge | Non pris en charge | Non pris en charge |
Single |
Number |
Pris en charge | Pris en charge | Pris en charge | Non pris en charge |
Double |
Number |
Pris en charge | Pris en charge | Pris en charge | Pris en charge |
IntPtr |
Number |
Pris en charge | Pris en charge | Pris en charge | Non pris en charge |
DateTime |
Date |
Pris en charge | Pris en charge | Non pris en charge | Non pris en charge |
DateTimeOffset |
Date |
Pris en charge | Pris en charge | Non pris en charge | Non pris en charge |
Exception |
Error |
Non pris en charge | Pris en charge | Pris en charge | Non pris en charge |
JSObject |
Object |
Non pris en charge | Pris en charge | Pris en charge | Pris en charge |
String |
String |
Non pris en charge | Pris en charge | Pris en charge | Pris en charge |
Object |
Any |
Non pris en charge | Pris en charge | Non pris en charge | Pris en charge |
Span<Byte> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Span<Int32> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Span<Double> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
ArraySegment<Byte> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
ArraySegment<Int32> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
ArraySegment<Double> |
MemoryView |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Task |
Promise |
Non pris en charge | Non pris en charge | Pris en charge | Non pris en charge |
Action |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Action<T1> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Action<T1, T2> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Action<T1, T2, T3> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Func<TResult> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Func<T1, TResult> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Func<T1, T2, TResult> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Func<T1, T2, T3, TResult> |
Function |
Non pris en charge | Non pris en charge | Non pris en charge | Non pris en charge |
Les conditions suivantes s’appliquent au mappage de type et aux valeurs marshalées :
- La colonne
Array of
indique si le type .NET peut être marshalé en tant que JSArray
. Exemple : C#int[]
(Int32
) mappé à JSArray
deNumber
. - Quand une valeur JS est passée en C# avec un type incorrect, le framework lève une exception dans la plupart des cas. Le framework n’effectue pas de contrôle de type au moment de la compilation en JS.
JSObject
,Exception
,Task
etArraySegment
créentGCHandle
et un proxy. Vous pouvez déclencher la suppression dans le code de développeur, ou autoriser le GC (nettoyage de la mémoire) .NET à supprimer les objets plus tard. Ces types entraînent une surcharge importante au niveau des performances.Array
: Le marshaling d’un tableau crée une copie du tableau en JS ou dans .NET.MemoryView
MemoryView
est une classe JS qui permet au runtime .NET WebAssembly de marshalerSpan
etArraySegment
.- Contrairement au marshaling d’un tableau, le marshaling de
Span
ouArraySegment
ne crée pas de copie de la mémoire sous-jacente. MemoryView
peut uniquement être correctement instancié par le runtime .NET WebAssembly. Il n’est donc pas possible d’importer une fonction JS en tant que méthode .NET qui a un paramètreSpan
ouArraySegment
.- Un
MemoryView
créé pour unSpan
est uniquement valide pour la durée de l’appel d’interopérabilité. Dans la mesure oùSpan
est alloué sur la pile des appels, qui ne persiste pas après l’appel d’interopérabilité, il n’est pas possible d’exporter une méthode .NET qui retourneSpan
. - Un
MemoryView
créé pour unArraySegment
survit après l’appel d’interopérabilité, et est utile pour partager de la mémoire tampon. L’appel dedispose()
sur unMemoryView
créé pourArraySegment
supprime le proxy, et dissocie le tableau .NET sous-jacent. Nous vous recommandons d’appelerdispose()
dans un bloctry-finally
pourMemoryView
.
Les fonctions accessibles sur l’espace de noms global peuvent être importées à l’aide du préfixe globalThis
dans le nom de la fonction et à l’aide de l’attribut [JSImport]
sans fournir de nom de module. Dans l’exemple suivant, console.log
est précédé de globalThis
. La fonction importée est appelée par la méthode C# Log
, qui accepte un message de chaîne C# (message
) et marshale la chaîne C# en JSString
pour console.log
:
[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);
Pour exporter une méthode .NET afin qu’elle puisse être appelée à partir de JS, utilisez JSExportAttribute.
Dans l’exemple suivant, la méthode Greeting
retourne une chaîne qui inclut le résultat de l’appel de la méthode GetHRef
. Comme cela a déjà été indiqué, la méthode C# GetHref
appelle JS pour la fonction window.location.href
à partir du module main.js
. window.location.href
retourne l’adresse (URL) de la page active :
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from {GetHRef()}";
Console.WriteLine(text);
return text;
}
Modèles de charge de travail et de projet expérimentaux
Pour illustrer la fonctionnalité d’interopérabilité JS et obtenir des modèles de projet d’interopérabilité JS, installez la charge de travail wasm-experimental
:
dotnet workload install wasm-experimental
La charge de travail wasm-experimental
contient deux modèles de projet : wasmbrowser
et wasmconsole
. Il s’agit de modèles expérimentaux pour le moment, ce qui signifie que le workflow de développeur associé aux modèles évolue. Toutefois, les API .NET et JS utilisées dans les modèles sont prises en charge dans .NET 7 et fournissent les bases de l’utilisation de .NET sur WASM à partir de JS.
Application de navigateur
Vous pouvez créer une application de navigateur avec le modèle wasmbrowser
. Cela vous permet de créer une application web illustrant l’utilisation simultanée de .NET et de JS dans un navigateur :
dotnet new wasmbrowser
Générez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :
dotnet build
L’application générée se trouve dans le répertoire bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
. L’espace réservé {BUILD CONFIGURATION}
correspond à la configuration de build (par exemple Debug
, Release
). L’espace réservé {TARGET FRAMEWORK}
correspond au moniker de framework cible (par exemple net7.0
).
Générez et exécutez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :
dotnet run
Vous pouvez également démarrer un serveur de fichiers statiques à partir du répertoire AppBundle
:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle
Dans l’exemple précédent, l’espace réservé {TARGET FRAMEWORK}
correspond au moniker de framework cible (par exemple net7.0
).
Application console Node.js
Vous pouvez créer une application console avec le modèle wasmconsole
. Cela vous permet de créer une application qui s’exécute sur WASM en tant qu’application console Node.js ou V8 :
dotnet new wasmconsole
Générez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :
dotnet build
L’application générée se trouve dans le répertoire bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
. L’espace réservé {BUILD CONFIGURATION}
correspond à la configuration de build (par exemple Debug
, Release
). L’espace réservé {TARGET FRAMEWORK}
correspond au moniker de framework cible (par exemple net7.0
).
Générez et exécutez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :
dotnet run
Vous pouvez également démarrer un serveur de fichiers statiques à partir du répertoire AppBundle
:
node bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle/main.mjs
Dans l’exemple précédent, l’espace réservé {TARGET FRAMEWORK}
correspond au moniker de framework cible (par exemple net7.0
).
Ressources supplémentaires
- Configuration et hébergement d’applications WebAssembly .NET
- Documentation de l’API
- Interopérabilité JavaScript JSImport/JSExport avec ASP.NET Core Blazor
- Dans le dépôt GitHub
dotnet/runtime
: - Utiliser .NET à partir d’une application JavaScript dans .NET 7
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour