Partager via


Interopérabilité JavaScript [JSImport]/[JSExport] avec un projet d’application de navigateur WebAssembly

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Avertissement

Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Cet article explique comment configurer un projet d’application de navigateur WebAssembly pour exécuter .NET à partir de JavaScript (JS) à l’aide de l’interopérabilité JS[JSImport]/[JSExport]. Pour plus d’informations et d’exemples, consultez Interopérabilité JavaScript `[JSImport]`/`[JSExport]` dans WebAssembly .NET.

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).

Les applications JS existantes peuvent tirer parti de la prise en charge étendue de WebAssembly côté client 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 si vous comptez uniquement exécuter l’application Blazor 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 (dernière version)

Installez la charge de travail wasm-tools, qui intègre les cibles MSBuild associées, dans un interpréteur de commandes d’administration :

dotnet workload install wasm-tools

Les outils peuvent également être installés via le programme d’installation de Visual Studio sous la charge de travail Développement web et ASP.NET dans le programme d’installation de Visual Studio. Sélectionnez l’option Outils de construction .NET WebAssembly dans la liste des composants facultatifs.

Si vous le souhaitez, installez la charge de travail wasm-experimental, qui ajoute les modèles de projet expérimentaux suivants :

  • Application de navigateur WebAssembly pour la prise en main de .NET sur WebAssembly dans une application de navigateur.
  • Application console WebAssembly pour la prise en main d’une application console basée sur Node.js.

Après avoir installé la charge de travail, ces nouveaux modèles peuvent être sélectionnés lors de la création d’un projet. 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

Les modèles peuvent également être installés à partir du package NuGet Microsoft.NET.Runtime.WebAssembly.Templates avec la commande suivante :

dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates

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 :

  • Configurez le moniker de framework cible (espace réservé {TARGET FRAMEWORK}) :

    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    

    .NET 7 (net7.0) ou version ultérieure est pris en charge.

  • 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.

Voici un exemple de fichier projet (.csproj) après la configuration. L’espace réservé {TARGET FRAMEWORK} est le framework cible :

<Project Sdk="Microsoft.NET.Sdk.WebAssembly">

  <PropertyGroup>
    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>
  • Définissez le moniker de framework cible :

    <TargetFramework>net7.0</TargetFramework>
    

    .NET 7 (net7.0) ou version ultérieure est pris en charge.

  • 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.

    Important

    Pour effectuer l’intégration à une application existante, copiez le contenu du dossier de sortie de publication† vers les ressources de déploiement de l’application existante pour qu’il soit mis à disposition avec le reste de l’application. Pour les déploiements en production, publiez l’application avec la commande dotnet publish -c Release dans un interpréteur de commandes, puis déployez le contenu du dossier de sortie avec l’application.

    † Le dossier de sortie de publication est l’emplacement cible de votre profil de publication. La valeur par défaut pour un profil Release dans .NET 8 ou version ultérieure est bin/Release/{TARGET FRAMEWORK}/publish, où l’espace réservé {TARGET FRAMEWORK} est le framework cible (par exemple, net8.0).

  • 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 fonction dom.setInnerText, qui accepte un sélectionneur d’élément et une heure pour afficher le temps actuel du chronomètre dans l’interface utilisateur. 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é avec JSImportAttribute (expliqué plus loin dans cet article). La fonction dom.setInnerText est importée dans C# et est appelée par la méthode C#SetInnerText. La méthode SetInnerText est présentée plus loin dans cette section.

  • exports.StopwatchSample.Reset() appelle .NET (StopwatchSample.Reset) à partir de JS. La méthode C# Reset redémarre le chronomètre s’il est en cours d’exécution, ou le réinitialise s’il n’est pas en cours d’exécution. La méthode Reset est présentée plus loin dans cette section.

  • exports.StopwatchSample.Toggle() appelle .NET (StopwatchSample.Toggle) à partir de JS. La méthode C# Toggle démarre ou arrête le chronomètre, selon qu’il est en cours d’exécution ou non. La méthode Toggle est présentée plus loin dans cette section.

  • runMain() exécute Program.Main.

  • setModuleImports associe un nom à un module de fonctions JS à des fins d’importation dans .NET. Le module JS contient une fonction window.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é avec JSImportAttribute (expliqué plus loin dans cet article). La fonction window.location.href est importée dans C# et est appelée par la méthode C#GetHRef. La méthode GetHRef 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 fonction window.location.href. La méthode Greeting est présentée plus loin dans cette section.

  • dotnet.run() exécute Program.Main.

Module 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();

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 dom.setInnerText est appelée à partir du module main.js quand la méthode SetInnerText est appelée :

[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);

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.

Pour plus d’informations et d’exemples de mappage de type, consultez Interopérabilité JavaScript `[JSImport]`/`[JSExport]` dans WebAssembly .NET.

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, chaque méthode est exportée vers JS, et peut être appelée à partir de fonctions JS :

  • La méthode Toggle démarre ou arrête le chronomètre, en fonction de son état d’exécution.
  • La méthode Reset redémarre le chronomètre s’il est en cours d’exécution, ou le réinitialise s’il n’est pas en cours d’exécution.
  • La méthode IsRunning indique si le chronomètre est en cours d’exécution.
[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;

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 JS et .NET utilisées dans les modèles sont prises en charge dans .NET 8, et fournissent les bases de l’utilisation de .NET sur WASM à partir de JS.

Les modèles peuvent également être installés à partir du package NuGet Microsoft.NET.Runtime.WebAssembly.Templates avec la commande suivante :

dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates

Application de navigateur

Vous pouvez créer une application de navigateur avec le modèle wasmbrowser à partir de la ligne de commande. 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

Dans Visual Studio, vous pouvez également créer l’application à l’aide du modèle de projet WebAssembly Browser App.

Générez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :

dotnet build

Générez et exécutez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :

dotnet run

En guise d’alternative, vous pouvez installer et utiliser la commande dotnet serve :

dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish

Dans l’exemple précédent, l’espace réservé {TARGET FRAMEWORK} correspond au moniker de framework cible.

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

Dans Visual Studio, vous pouvez également créer l’application à l’aide du modèle de projet WebAssembly Console App.

Générez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :

dotnet build

Générez et exécutez l’application à partir de Visual Studio ou à l’aide de l’interface .NET CLI :

dotnet run

En guise d’alternative, vous pouvez démarrer n’importe quel serveur de fichiers statiques à partir du répertoire de sortie de publication qui contient le fichier main.mjs :

node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs

Dans l’exemple précédent, l’espace réservé {TARGET FRAMEWORK} est le moniker de framework cible, et l’espace réservé {PATH} est le chemin d’accès au fichier main.mjs.

Ressources supplémentaires