Delen via


JavaScript [JSImport]/[JSExport] interop met een WebAssembly Browser App-project

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

In dit artikel wordt uitgelegd hoe u een WebAssembly Browser App-project instelt om .NET uit te voeren vanuit JavaScript (JS) met behulp van JS[JSImport]/[JSExport] interop. Zie JavaScript [JSImport]/[JSExport]' interop in .NET WebAssemblyvoor meer informatie en voorbeelden.

Zie de .NET WebAssembly-toepassingen configureren en hosten richtlijnen in de GitHub-opslagplaats voor .NET Runtime (dotnet/runtime) voor aanvullende richtlijnen.

Bestaande JS-apps kunnen gebruikmaken van de uitgebreide client-side WebAssembly-ondersteuning om .NET-bibliotheken vanuit JS opnieuw te gebruiken of om nieuwe .NET-gebouwde apps en frameworks te ontwikkelen.

Notitie

Dit artikel is gericht op het uitvoeren van .NET vanuit JS-apps zonder enige afhankelijkheid van Blazor. Voor hulp bij het gebruik van [JSImport]/[JSExport] interop in Blazor WebAssembly-apps, zie JavaScript JSImport/JSExport interop met ASP.NET Core Blazor.

Deze benaderingen zijn geschikt wanneer u alleen verwacht dat de Blazor-app wordt uitgevoerd op WebAssembly (WASM). Bibliotheken kunnen een runtimecontrole uitvoeren om te bepalen of de app wordt uitgevoerd op WASM door OperatingSystem.IsBrowseraan te roepen.

Voorwaarden

.NET SDK (nieuwste versie)

Installeer de wasm-tools workload in een beheeropdrachtshell, die de gerelateerde MSBuild-doelen bevat:

dotnet workload install wasm-tools

De hulpprogramma's kunnen ook worden geïnstalleerd via het installatieprogramma van Visual Studio onder de ASP.NET en webontwikkeling workload in het installatieprogramma van Visual Studio. Selecteer in de lijst met optionele onderdelen de optie .NET WebAssembly-buildhulpprogramma's.

Installeer eventueel de wasm-experimental workload, waarmee de volgende experimentele projectsjablonen worden toegevoegd:

  • WebAssembly Browser-app om aan de slag te gaan met .NET op WebAssembly in een browser-app.
  • WebAssembly-console-app om aan de slag te gaan in een console-app op basis van Node.js.

Na de installatie van de workload kunnen deze nieuwe sjablonen worden geselecteerd bij het maken van een nieuw project. Deze workload is niet vereist als u van plan bent om JS[JSImport]/[JSExport] interoperabiliteit te integreren in een bestaande JS-app.

dotnet workload install wasm-experimental

De sjablonen kunnen ook worden geïnstalleerd vanuit het Microsoft.NET.Runtime.WebAssembly.Templates NuGet-pakket met de volgende opdracht:

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

Zie de sectie Experimentele workload en projectsjablonen voor meer informatie.

Namespace

De JS interop-API die in dit artikel wordt beschreven, wordt beheerd door kenmerken in de System.Runtime.InteropServices.JavaScript naamruimte.

Projectconfiguratie

Een project (.csproj) configureren om JS interop in te schakelen:

  • Stel de doelframeworkmoniker in bij ({TARGET FRAMEWORK} tijdelijke aanduiding):

    <TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
    

    .NET 7 (net7.0) of hoger wordt ondersteund.

  • Schakel de eigenschap AllowUnsafeBlocks in, waarmee de codegenerator in de Roslyn-compiler pointers kan gebruiken voor JS interop:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Waarschuwing

    Voor de JS interop-API moet AllowUnsafeBlocksworden ingeschakeld. Wees voorzichtig bij het implementeren van uw eigen onveilige code in .NET-apps, waardoor beveiligings- en stabiliteitsrisico's kunnen optreden. Zie Onveilige code, aanwijzertypen en functiepointersvoor meer informatie.

Hier volgt een voorbeeld van een projectbestand (.csproj) na de configuratie. De tijdelijke aanduiding {TARGET FRAMEWORK} is het doelframework:

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

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

</Project>
  • Stel de doelframework-moniker in:

    <TargetFramework>net7.0</TargetFramework>
    

    .NET 7 (net7.0) of hoger wordt ondersteund.

  • Geef browser-wasm op voor de runtime-id:

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • Geef een uitvoertype van het uitvoerbare bestand op:

    <OutputType>Exe</OutputType>
    
  • Schakel de eigenschap AllowUnsafeBlocks in, waarmee de codegenerator in de Roslyn-compiler pointers kan gebruiken voor JS interop:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Waarschuwing

    Voor de JS interop-API moet AllowUnsafeBlocksworden ingeschakeld. Wees voorzichtig bij het implementeren van uw eigen onveilige code in .NET-apps, waardoor beveiligings- en stabiliteitsrisico's kunnen optreden. Zie Onveilige code, aanwijzertypen en functiepointersvoor meer informatie.

  • Geef WasmMainJSPath op om naar een bestand op schijf te verwijzen. Dit bestand wordt gepubliceerd met de app, maar het gebruik van het bestand is niet vereist als u .NET in een bestaande JS-app integreert.

    In het volgende voorbeeld is het JS bestand op schijf main.js, maar een JS bestandsnaam is toegestaan:

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

Voorbeeldprojectbestand (.csproj) na configuratie:

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

JavaScript-interoperabiliteit in WASM

API's in het volgende voorbeeld worden geïmporteerd uit dotnet.js. Met deze API's kunt u benoemde modules instellen die kunnen worden geïmporteerd in uw C#-code en methoden aanroepen die worden weergegeven door uw .NET-code, inclusief Program.Main.

Belangrijk

'Importeren' en 'exporteren' in dit artikel worden gedefinieerd vanuit het perspectief van .NET:

  • Een app importeert JS methoden zodat deze kunnen worden aangeroepen vanuit .NET.
  • De app exporteert .NET-methoden, zodat ze kunnen worden aangeroepen vanuit JS.

In het volgende voorbeeld:

  • Het dotnet.js-bestand wordt gebruikt om de .NET WebAssembly-runtime te maken en te starten. dotnet.js wordt gegenereerd als onderdeel van de build-uitvoer van de app.

    Belangrijk

    Als u wilt integreren met een bestaande app, kopieert u de inhoud van de uitvoermap voor publiceren† naar de implementatieassets van de bestaande app, zodat deze samen met de rest van de app kan worden geleverd. Voor productie-implementaties publiceert u de app met de opdracht dotnet publish -c Release in een opdrachtshell en implementeert u de inhoud van de uitvoermap met de app.

    †De uitvoermap Publiceren is de doellocatie van uw publicatieprofiel. De standaardinstelling voor een Release-profiel in .NET 8 of hoger is bin/Release/{TARGET FRAMEWORK}/publish, waarbij de tijdelijke aanduiding {TARGET FRAMEWORK} het doelframework is (bijvoorbeeld net8.0).

  • dotnet.create() stelt de .NET WebAssembly-runtime in.

  • setModuleImports een naam koppelt aan een module van JS functies voor importeren in .NET. De JS-module bevat een dom.setInnerText-functie die een elementselector en een tijd ontvangen om de huidige stopwatchtijd in de gebruikersinterface weer te geven. De naam van de module kan elke tekenreeks zijn (deze hoeft geen bestandsnaam te zijn), maar moet overeenkomen met de naam die wordt gebruikt met de JSImportAttribute (verderop in dit artikel uitgelegd). De dom.setInnerText-functie wordt geïmporteerd in C# en aangeroepen door de C#-methode SetInnerText. De methode SetInnerText wordt verderop in deze sectie weergegeven.

  • exports.StopwatchSample.Reset() roept aan in .NET (StopwatchSample.Reset) vanuit JS. De Reset C#-methode start de stopwatch opnieuw op als deze wordt uitgevoerd of opnieuw wordt ingesteld als deze niet wordt uitgevoerd. De methode Reset wordt verderop in deze sectie weergegeven.

  • exports.StopwatchSample.Toggle() roept .NET aan (StopwatchSample.Toggle) vanuit JS. De Toggle C#-methode start of stopt de stopwatch, afhankelijk van of de stopwatch momenteel loopt of niet. De methode Toggle wordt verderop in deze sectie weergegeven.

  • runMain() voert Program.Mainuit.

  • setModuleImports een naam koppelt aan een module van JS functies voor importeren in .NET. De JS-module bevat een window.location.href functie, die het huidige paginaadres (URL) retourneert. De naam van de module kan elke tekenreeks zijn (deze hoeft geen bestandsnaam te zijn), maar moet overeenkomen met de naam die wordt gebruikt met de JSImportAttribute (verderop in dit artikel uitgelegd). De window.location.href-functie wordt geïmporteerd in C# en aangeroepen door de C#-methode GetHRef. De methode GetHRef wordt verderop in deze sectie weergegeven.

  • exports.MyClass.Greeting() roept .NET (MyClass.Greeting) aan vanuit JS. De methode Greeting C# retourneert een tekenreeks die het resultaat bevat van het aanroepen van de window.location.href-functie. De methode Greeting wordt verderop in deze sectie weergegeven.

  • dotnet.run() wordt Program.Mainuitgevoerd.

JS module:

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

Als u een JS-functie wilt importeren zodat deze vanuit C# kan worden aangeroepen, gebruikt u de nieuwe JSImportAttribute op een handtekening voor overeenkomende methoden. De eerste parameter voor de JSImportAttribute is de naam van de JS-functie die moet worden geïmporteerd en de tweede parameter is de naam van de module.

In het volgende voorbeeld wordt de functie dom.setInnerText aangeroepen vanuit de main.js-module wanneer SetInnerText methode wordt aangeroepen:

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

In het volgende voorbeeld wordt de functie window.location.href aangeroepen vanuit de main.js-module wanneer GetHRef methode wordt aangeroepen:

[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();

In de signatuur van de geïmporteerde methode kunt u .NET-typen gebruiken voor parameters en retourwaarden, die automatisch worden gemarshald door de runtime. Gebruik JSMarshalAsAttribute<T> om te bepalen hoe de geïmporteerde methodeparameters worden verwerkt. U kunt er bijvoorbeeld voor kiezen om een long te converteren naar System.Runtime.InteropServices.JavaScript.JSType.Number of System.Runtime.InteropServices.JavaScript.JSType.BigInt. U kunt Action/Func<TResult> callbacks doorgeven als parameters, die worden omgezet als aanroepbare JS functies. U kunt zowel JS als beheerde objectverwijzingen doorgeven, en ze worden verwerkt als proxyobjecten, waardoor het object actief blijft over de procesgrens totdat de proxy wordt opgeruimd door de garbage collector. U kunt asynchrone methoden ook importeren en exporteren met een Task resultaat, dat als JS promisesworden gemarshald. De meeste marshalltypen werken in beide richtingen, als parameters en als retourwaarden, op zowel geïmporteerde als geëxporteerde methoden.

Voor aanvullende informatie en voorbeelden van de toewijzing van typen, zie JavaScript `[JSImport]`/`[JSExport]` interop in .NET WebAssembly.

Functies die toegankelijk zijn voor de globale naamruimte kunnen worden geïmporteerd met behulp van het globalThis voorvoegsel in de functienaam en met behulp van het kenmerk [JSImport] zonder een modulenaam op te geven. In het volgende voorbeeld wordt console.log voorafgegaan door globalThis. De geïmporteerde functie wordt aangeroepen door de C#-methode Log, die een C#-tekenreeksbericht (message) accepteert en de C#-tekenreeks omzet naar een JSString voor console.log:

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

Als u een .NET-methode wilt exporteren zodat deze kan worden aangeroepen vanuit JS, gebruikt u de JSExportAttribute.

In het volgende voorbeeld wordt elke methode geëxporteerd naar JS en kan deze worden aangeroepen vanuit JS functies:

  • De methode Toggle start of stopt de stopwatch, afhankelijk van de actieve status.
  • De methode Reset start de stopwatch opnieuw op als deze wordt uitgevoerd of opnieuw wordt ingesteld als deze niet wordt uitgevoerd.
  • De methode IsRunning geeft aan of de stopwatch loopt.
[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;

In het volgende voorbeeld retourneert de methode Greeting een tekenreeks die het resultaat bevat van het aanroepen van de methode GetHRef. Zoals eerder wordt weergegeven, roept de methode GetHref C#JS aan voor de window.location.href-functie uit de main.js-module. window.location.href retourneert het huidige paginaadres (URL):

[JSExport]
internal static string Greeting()
{
    var text = $"Hello, World! Greetings from {GetHRef()}";
    Console.WriteLine(text);
    return text;
}

Experimentele workload- en projectsjablonen

Installeer de wasm-experimental workload om de JS interop-functionaliteit te demonstreren en JS interoperabiliteitsprojectsjablonen te verkrijgen:

dotnet workload install wasm-experimental

De workload wasm-experimental bevat twee projectsjablonen: wasmbrowser en wasmconsole. Deze sjablonen zijn momenteel experimenteel, wat betekent dat de werkstroom voor ontwikkelaars voor de sjablonen zich ontwikkelt. De .NET- en JS API's die in de sjablonen worden gebruikt, worden echter ondersteund in .NET 8 en bieden een basis voor het gebruik van .NET op WASM uit JS.

De sjablonen kunnen ook worden geïnstalleerd vanuit het Microsoft.NET.Runtime.WebAssembly.Templates NuGet-pakket met de volgende opdracht:

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

Browserapp

U kunt een browser-app maken met de wasmbrowser-sjabloon vanaf de opdrachtregel, waarmee een web-app wordt gemaakt die laat zien hoe u .NET gebruikt en JS samen in een browser:

dotnet new wasmbrowser

U kunt ook in Visual Studio de app maken met behulp van de WebAssembly Browser App projectsjabloon.

Bouw de app vanuit Visual Studio of met behulp van de .NET CLI:

dotnet build

Bouw en voer de app uit vanuit Visual Studio of met behulp van de .NET CLI:

dotnet run

U kunt ook de opdracht dotnet serve installeren en gebruiken:

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

In het voorgaande voorbeeld is de tijdelijke aanduiding {TARGET FRAMEWORK} het doelframework.

Node.js console-applicatie

U kunt een console-app maken met de wasmconsole-sjabloon, waarmee een app wordt gemaakt die wordt uitgevoerd onder WASM als een Node.js of V8 console-app:

dotnet new wasmconsole

U kunt ook in Visual Studio de app maken met behulp van de WebAssembly Console App projectsjabloon.

Bouw de app vanuit Visual Studio of met behulp van de .NET CLI:

dotnet build

Bouw en voer de app uit vanuit Visual Studio of met behulp van de .NET CLI:

dotnet run

U kunt ook een statische bestandsserver starten vanuit de uitvoermap van publiceren die het main.mjs-bestand bevat.

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

In het vorige voorbeeld is de plaatsaanduiding {TARGET FRAMEWORK} het -doelframeworken is {PATH} het pad naar het main.mjs-bestand.

Aanvullende informatiebronnen