Freigeben über


Ausführen von .NET über JavaScript

In diesem Artikel wird erläutert, wie .NET über JavaScript (JS) mithilfe der JS-Interoperabilität durch [JSImport]/[JSExport] ausgeführt wird.

Weitere Anleitungen finden Sie im Leitfaden Konfigurieren und Hosten von .NET WebAssembly-Anwendungen im .NET Runtime-GitHub-Repository (dotnet/runtime). Wir planen, diesen Artikel Ende 2023 oder Anfang 2024 zu aktualisieren, um neue Informationen in den verlinkten Leitfaden aufzunehmen.

Vorhandene JS-Apps können die erweiterte clientseitige WebAssembly-Unterstützung in .NET 7 oder höher nutzen, um .NET-Bibliotheken aus JS wiederzuverwenden oder neue .NET-basierte Apps und Frameworks zu erstellen.

Hinweis

In diesem Artikel liegt der Schwerpunkt auf der Ausführung von .NET aus JS-Apps ohne Abhängigkeiten von Blazor. Einen Leitfaden zur Verwendung der Interoperabilität durch [JSImport]/[JSExport] in Blazor WebAssembly-Apps finden Sie unter JavaScript-Interoperabilität durch JSImport/JSExport mit ASP.NET Core Blazor.

Diese Ansätze eignen sich, wenn die Ausführung nur in der WebAssembly (WASM) erfolgen soll. Bibliotheken können durch Aufruf von OperatingSystem.IsBrowser eine Laufzeitüberprüfung vornehmen, um zu ermitteln, ob die App über WASM ausgeführt wird.

Voraussetzungen

.NET 7.0 SDK

Installieren Sie die neueste Version des .NET SDK.

Installieren Sie die wasm-tools-Workload, die die zugehörigen MSBuild-Ziele enthält.

dotnet workload install wasm-tools

Installieren Sie optional die wasm-experimental-Workload, die experimentelle Projektvorlagen für die ersten Schritte mit .NET über die WebAssembly in einer Browser-App (WebAssembly-Browser-App) oder in einer auf Node.js basierenden Konsolen-App (WebAssembly-Konsolen-App) enthält. Diese Workload ist nicht erforderlich, wenn Sie planen, die JS-Interoperabilität durch [JSImport]/[JSExport] in eine vorhandene JS-App zu integrieren.

dotnet workload install wasm-experimental

Weitere Informationen finden Sie im Abschnitt Experimentelle Workload und Projektvorlagen.

Namespace

Die in diesem Artikel beschriebene JS-Interop-API wird durch Attribute im Namespace System.Runtime.InteropServices.JavaScript gesteuert.

Projektkonfiguration

So konfigurieren Sie ein Projekt (.csproj) zum Aktivieren der JS-Interoperabilität

  • Verwenden Sie als Ziel net7.0 oder höher:

    <TargetFramework>net7.0</TargetFramework>
    
  • Geben Sie als Runtimebezeichner browser-wasm an:

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • Geben Sie einen ausführbaren Ausgabetyp an:

    <OutputType>Exe</OutputType>
    
  • Aktivieren Sie die AllowUnsafeBlocks-Eigenschaft, damit der Codegenerator im Roslyn-Compiler Zeiger für die JS-Interoperabilität verwenden kann:

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Warnung

    Die JS-Interop-API erfordert die Aktivierung von AllowUnsafeBlocks. Seien Sie vorsichtig, wenn Sie Ihren eigenen unsicheren Code in .NET-Anwendungen implementieren, da dies zu Sicherheits- und Stabilitätsrisiken führen kann. Weitere Informationen finden Sie unter Unsicherer Code, Zeigertypen und Funktionszeiger.

  • Geben Sie als WasmMainJSPath einen Verweis auf eine Datei auf dem Datenträger an. Diese Datei wird mit der App veröffentlicht, die Verwendung der Datei ist jedoch nicht erforderlich, wenn Sie .NET in eine vorhandene JS-App integrieren.

    Im folgenden Beispiel befindet sich die JS-Datei auf dem Datenträger main.js, es ist aber jeder JS-Dateiname zulässig:

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

Beispielprojektdatei (.csproj) nach der Konfiguration:

<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-Interoperabilität in WASM

Die APIs im folgenden Beispiel werden aus dotnet.js importiert. Mit diesen APIs können Sie benannte Module einrichten, die in Ihren C#-Code importiert und in Methoden aufgerufen werden können, die von Ihrem .NET-Code verfügbar gemacht werden, einschließlich Program.Main.

Wichtig

In diesem Artikel werden die Begriffe „Importieren“ und „Exportieren“ aus der Perspektive von .NET definiert:

  • Eine App importiert JS-Methoden, damit sie in .NET aufgerufen werden können.
  • Eine App exportiert .NET-Methoden, damit sie in JS aufgerufen werden können.

Im folgenden Beispiel:

  • Die Datei dotnet.js wird verwendet, um die .NET-WebAssembly-Runtime zu erstellen und zu starten. dotnet.js wird als Teil der Buildausgabe der App generiert und befindet sich im Ordner AppBundle:

    bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle

    Der Platzhalter {BUILD CONFIGURATION} ist die Buildkonfiguration (z. B. Debug, Release), und der Platzhalter {TARGET FRAMEWORK} ist das Zielframework (z. B. net7.0).

    Wichtig

    Für die Integration mit einer vorhandenen App kopieren Sie den Inhalt des Ordners AppBundle, damit er zusammen mit der restlichen App bereitgestellt werden kann. Veröffentlichen Sie für Produktionsbereitstellungen die App mit dem Befehl dotnet publish -c Release in einer Befehlsshell, und stellen Sie den Ordner AppBundle mit der App bereit.

  • dotnet.create() richtet die .NET WebAssembly-Runtime ein.

  • setModuleImports ordnet einen Namen einem Modul von JS-Funktionen für den Import in .NET zu. Das JS-Modul enthält eine window.location.href-Funktion, die die aktuelle Seitenadresse (URL) zurückgibt. Der Name des Moduls kann eine beliebige Zeichenfolge sein (es muss kein Dateiname sein), aber er muss mit dem Namen übereinstimmen, der für das JSImportAttribute verwendet wird (Eine Erläuterung finden Sie weiter unten in diesem Artikel.). Die window.location.href-Funktion wird in C# importiert und von der C#-Methode GetHRef aufgerufen. Die GetHRef-Methode wird später in diesem Abschnitt beschrieben.

  • exports.MyClass.Greeting() führt Aufrufe in .NET (MyClass.Greeting) von JS durch. Die C#-Methode Greeting gibt eine Zeichenfolge zurück, die das Ergebnis des Aufrufs der window.location.href-Funktion enthält. Die Greeting-Methode wird später in diesem Abschnitt beschrieben.

  • dotnet.run() führt Program.Main aus.

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

Um eine JS-Funktion zu importieren, damit sie in C# aufgerufen werden kann, verwenden Sie das neue JSImportAttribute in einer übereinstimmenden Methodensignatur. Der erste Parameter des JSImportAttribute ist der Name der zu importierenden JS-Funktion, und der zweite Parameter ist der Name des Moduls.

Im folgenden Beispiel wird die window.location.href-Funktion aus dem Modul main.js aufgerufen, wenn die GetHRef-Methode aufgerufen wird:

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

In der importierten Methodensignatur können Sie .NET-Typen für Parameter und Rückgabewerte verwenden, die automatisch von der Laufzeit gemarshallt werden. Sie können mit JSMarshalAsAttribute<T> steuern, wie die importierten Methodenparameter gemarshallt werden. Beispielsweise können Sie entscheiden, ob ein Wert vom Typ long als System.Runtime.InteropServices.JavaScript.JSType.Number oder System.Runtime.InteropServices.JavaScript.JSType.BigInt gemarshallt wird. Sie können Action/Func<TResult>-Rückrufe als Parameter übergeben, die als aufrufbare JS-Funktionen gemarshallt werden. Sie können sowohl JS als auch verwaltete Objektverweise übergeben. Diese werden als Proxyobjekte gemarshallt, wobei das Objekt über die Grenze hinweg aktiv bleibt, bis beim Proxy eine Garbage Collection durchgeführt wird. Zudem können asynchrone Methoden mit einem Task-Ergebnis importiert und exportiert werden. Die Ergebnisse werden als JSZusage gemarshallt. Die meisten der gemarshallten Typen funktionieren in beide Richtungen, als Parameter und als Rückgabewerte, und sowohl bei importierten als auch bei exportierten Methoden.

In der folgenden Tabelle werden die unterstützten Typzuordnungen gezeigt.

.NET JavaScript Nullable Task zu Promise JSMarshalAs optional Array of
Boolean Boolean Unterstützt Unterstützt Unterstützt Nicht unterstützt
Byte Number Unterstützt Unterstützt Unterstützt Unterstützt
Char String Unterstützt Unterstützt Unterstützt Nicht unterstützt
Int16 Number Unterstützt Unterstützt Unterstützt Nicht unterstützt
Int32 Number Unterstützt Unterstützt Unterstützt Unterstützt
Int64 Number Unterstützt Unterstützt Nicht unterstützt Nicht unterstützt
Int64 BigInt Unterstützt Unterstützt Nicht unterstützt Nicht unterstützt
Single Number Unterstützt Unterstützt Unterstützt Nicht unterstützt
Double Number Unterstützt Unterstützt Unterstützt Unterstützt
IntPtr Number Unterstützt Unterstützt Unterstützt Nicht unterstützt
DateTime Date Unterstützt Unterstützt Nicht unterstützt Nicht unterstützt
DateTimeOffset Date Unterstützt Unterstützt Nicht unterstützt Nicht unterstützt
Exception Error Nicht unterstützt Unterstützt Unterstützt Nicht unterstützt
JSObject Object Nicht unterstützt Unterstützt Unterstützt Unterstützt
String String Nicht unterstützt Unterstützt Unterstützt Unterstützt
Object Any Nicht unterstützt Unterstützt Nicht unterstützt Unterstützt
Span<Byte> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Span<Int32> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Span<Double> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
ArraySegment<Byte> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
ArraySegment<Int32> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
ArraySegment<Double> MemoryView Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Task Promise Nicht unterstützt Nicht unterstützt Unterstützt Nicht unterstützt
Action Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Action<T1> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Action<T1, T2> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Action<T1, T2, T3> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Func<TResult> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Func<T1, TResult> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Func<T1, T2, TResult> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt
Func<T1, T2, T3, TResult> Function Nicht unterstützt Nicht unterstützt Nicht unterstützt Nicht unterstützt

Die folgenden Bedingungen gelten für Typzuordnungen und gemarshallte Werte:

  • Die Spalte Array of gibt an, ob der .NET-Typ als JSArray gemarshallt werden kann. Beispiel: C# int[] (Int32) zugeordnet zu JSArray von Number.
  • Wenn ein JS-Wert vom falschen Typ an C# übergeben wird, löst das Framework in den meisten Fällen eine Ausnahme aus. Das Framework führt zur Kompilierzeit keine Typüberprüfung in JS durch.
  • JSObject, Exception, Task und ArraySegment erstellen GCHandle und einen Proxy. Sie können die Entsorgung im Entwicklercode auslösen oder es der .NET-Garbage Collection (GC) überlassen, die Objekte später zu entfernen. Diese Typen bedeuten einen erheblichen Leistungsaufwand.
  • Array: Beim Marshallen eines Arrays wird eine Kopie des Arrays in JS oder .NET erstellt.
  • MemoryView
    • MemoryView ist eine JS-Klasse für die .NET-WebAssembly-Runtime zum Marshallen von Span und ArraySegment.
    • Im Gegensatz zum Marshallen eines Arrays wird beim Marshallen einer Span oder eines ArraySegment keine Kopie des zugrunde liegenden Speichers erstellt.
    • MemoryView kann nur von der .NET-WebAssembly-Runtime ordnungsgemäß instanziiert werden. Daher ist es nicht möglich, eine JS-Funktion als .NET-Methode zu importieren, die über den Parameter Span oder ArraySegment verfügt.
    • Wenn MemoryView für einen Span erstellt wird, gilt sie nur für die Dauer des Interoperabilitätsaufrufs. Da Span der Aufrufliste zugewiesen wird, die nach dem Interoperabilitätsaufruf nicht beibehalten wird, ist es nicht möglich, eine .NET-Methode zu exportieren, die einen Span zurückgibt.
    • MemoryView wird für ein ArraySegment erstellt und auch nach dem Interoperabilitätsaufruf beibehalten und ist damit für die gemeinsame Verwendung eines Puffers nützlich. Durch Aufrufen von dispose() in einer MemoryView, die für ein ArraySegment erstellt wurde, wird der Proxy verworfen und das zugrunde liegende .NET-Array getrennt. Es wird empfohlen, dispose() in einem try-finally-Block für MemoryView aufzurufen.

Funktionen, auf die im globalen Namespace zugegriffen werden kann, können mithilfe des Präfixes globalThis im Funktionsnamen und mithilfe des Attributs [JSImport] ohne Angabe eines Modulnamens importiert werden. Im folgenden Beispiel hat console.log das Präfix globalThis. Die importierte Funktion wird von der C#-Log Methode aufgerufen, die eine C#-Zeichenfolgenmeldung (message) akzeptiert und die C#-Zeichenfolge in einen JSString für console.log marshallt:

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

Verwenden Sie das JSExportAttribute, um eine .NET-Methode zu exportieren, damit sie über JS aufgerufen werden kann.

Im folgenden Beispiel gibt die Greeting-Methode eine Zeichenfolge zurück, die das Ergebnis des Aufrufs der GetHRef-Methode enthält. Wie zuvor gezeigt, ruft die C#-Methode GetHref die window.location.href-Funktion in JS aus dem Modul main.js auf. window.location.href gibt die aktuelle Seitenadresse (URL) zurück:

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

    return text;
}

Experimentelle Workload und Projektvorlagen

Installieren Sie die Workload wasm-experimental, um die JS-Interoperabilität zu veranschaulichen und die Projektvorlagen für die JS-Interoperabilität abzurufen:

dotnet workload install wasm-experimental

Die Workload wasm-experimental enthält die beiden Projektvorlagen wasmbrowser und wasmconsole. Diese Vorlagen sind derzeit experimentell. Dies bedeutet, dass sich der Entwicklerworkflow für die Vorlagen noch in der Entwicklung befindet. Die in den Vorlagen verwendeten .NET- und JS-APIs werden jedoch in .NET 7 unterstützt und bieten eine Grundlage für die Verwendung von .NET in WASM von JS.

Browser-App

Sie können mit der Vorlage wasmbrowser eine Browser-App erstellen, die eine Web-App erstellt, mit der die Verwendung von .NET und JS zusammen in einem Browser veranschaulicht werden kann:

dotnet new wasmbrowser

Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI:

dotnet build

Sie finden die erstellte App im Verzeichnis bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle. Der Platzhalter {BUILD CONFIGURATION} ist die Buildkonfiguration (z. B. Debug, Release). Der Platzhalter {TARGET FRAMEWORK} ist der Zielframeworkmoniker (z. B. net7.0).

Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI, und führen Sie sie aus:

dotnet run

Alternativ können Sie einen beliebigen statischen Dateiserver aus dem Verzeichnis AppBundle starten:

dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle

Im vorherigen Beispiel ist der Platzhalter {TARGET FRAMEWORK} der Zielframeworkmoniker (z. B. net7.0).

NET-Konsolenanwendung

Sie können eine Konsolen-App mit der Vorlage wasmconsole erstellen, die eine App erstellt, die unter WASM als Node.js- oder V8-Konsolen-App ausgeführt wird:

dotnet new wasmconsole

Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI:

dotnet build

Sie finden die erstellte App im Verzeichnis bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle. Der Platzhalter {BUILD CONFIGURATION} ist die Buildkonfiguration (z. B. Debug, Release). Der Platzhalter {TARGET FRAMEWORK} ist der Zielframeworkmoniker (z. B. net7.0).

Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI, und führen Sie sie aus:

dotnet run

Alternativ können Sie einen beliebigen statischen Dateiserver aus dem Verzeichnis AppBundle starten:

node bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle/main.mjs

Im vorherigen Beispiel ist der Platzhalter {TARGET FRAMEWORK} der Zielframeworkmoniker (z. B. net7.0).

Zusätzliche Ressourcen