JavaScript [JSImport]
/[JSExport]
Interop mit einem WebAssembly Browser App Projekt
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
In diesem Artikel wird erklärt, wie man ein WebAssembly Browser App-Projekt einrichtet, um .NET von JavaScript (JS) aus unter Verwendung von JS[JSImport]
/[JSExport]
Interop auszuführen. Weitere Informationen und Beispiele finden Sie unter JavaScript-Interoperabilität mit `[JSImport]`/`[JSExport]` in .NET WebAssembly.
Weitere Anleitungen finden Sie im Leitfaden Konfigurieren und Hosten von .NET WebAssembly-Anwendungen im .NET Runtime-GitHub-Repository (dotnet/runtime
).
Vorhandene JS-Apps können die erweiterte clientseitige WebAssembly-Unterstützung nutzen, um .NET-Bibliotheken von 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 sind geeignet, wenn Sie nur erwarten, dass die Blazor App auf WebAssembly (WASM) läuft. 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
Installieren Sie die wasm-tools
-Workload in einer administrativen Befehlsshell mit den zugehörigen MSBuild-Zielen:
dotnet workload install wasm-tools
Die Tools können auch über die Workload ASP.NET- und Webentwicklung im Visual Studio-Installer installiert werden. Wählen Sie die Option .NET-WebAssembly-Buildtools aus der Liste der optionalen Komponenten aus.
Optional können Sie den wasm-experimental
-Workload installieren, der die folgenden experimentellen Projektvorlagen hinzufügt:
- WebAssembly Browser App für die ersten Schritte mit .NET auf WebAssembly in einer Browser App.
- WebAssembly Console App für den Einstieg in eine Node.js-basierte Console App.
Nach der Installation des Workloads können diese neuen Vorlagen bei der Erstellung eines neuen Projekts ausgewählt werden. 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
Die Vorlagen können auch mit dem folgenden Befehl über das NuGet-Paket Microsoft.NET.Runtime.WebAssembly.Templates
installiert werden:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
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
Legen Sie den Zielframeworkmoniker fest (Platzhalter
{TARGET FRAMEWORK}
):<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
.NET 7 (
net7.0
) oder höher wird unterstützt.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.
Das folgende Beispiel zeigt eine Projektdatei (.csproj
) nach der Konfiguration. Der Platzhalter {TARGET FRAMEWORK}
ist das Zielframework:
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
Legen Sie den Zielframeworkmoniker fest:
<TargetFramework>net7.0</TargetFramework>
.NET 7 (
net7.0
) oder höher wird unterstützt.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.Wichtig
Für die Integration mit einer vorhandenen App kopieren Sie den Inhalt des Ausgabeordners der Veröffentlichung† in die Bereitstellungsressourcen der vorhandenen App, damit sie zusammen mit dem Rest der App bereitgestellt werden können. Veröffentlichen Sie für Produktionsbereitstellungen die App mit dem Befehl
dotnet publish -c Release
in einer Befehlsshell, und stellen Sie den Inhalt des Ausgabeordners mit der App bereit.† Der Ausgabeordner der Veröffentlichung ist der Zielspeicherort Ihres Veröffentlichungsprofils. Der Standardwert für ein Release-Profil in .NET 8 oder höher ist
bin/Release/{TARGET FRAMEWORK}/publish
, wobei der Platzhalter{TARGET FRAMEWORK}
das Zielframework ist (z. B.net8.0
).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 einedom.setInnerText
-Funktion, die den Elementselektor und die Zeit zum Anzeigen der aktuellen Stoppuhrzeit auf der Benutzeroberfläche akzeptiert. Der Name des Moduls kann eine beliebige Zeichenfolge sein (es muss kein Dateiname sein), aber er muss mit dem Namen übereinstimmen, der für dasJSImportAttribute
verwendet wird (Eine Erläuterung finden Sie weiter unten in diesem Artikel.). Diedom.setInnerText
-Funktion wird in C# importiert und von der C#-MethodeSetInnerText
aufgerufen. DieSetInnerText
-Methode wird später in diesem Abschnitt beschrieben.exports.StopwatchSample.Reset()
führt Aufrufe in .NET (StopwatchSample.Reset
) von JS durch. Die C#-MethodeReset
startet die Stoppuhr neu, wenn sie ausgeführt wird, oder setzt sie zurück, wenn sie nicht ausgeführt wird. DieReset
-Methode wird später in diesem Abschnitt beschrieben.exports.StopwatchSample.Toggle()
führt Aufrufe in .NET (StopwatchSample.Toggle
) von JS durch. Die C#-MethodeToggle
startet oder unterbricht die Stoppuhr, je nachdem, ob sie derzeit ausgeführt wird. DieToggle
-Methode wird später in diesem Abschnitt beschrieben.runMain()
führtProgram.Main
aus.
setModuleImports
ordnet einen Namen einem Modul von JS-Funktionen für den Import in .NET zu. Das JS-Modul enthält einewindow.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 dasJSImportAttribute
verwendet wird (Eine Erläuterung finden Sie weiter unten in diesem Artikel.). Diewindow.location.href
-Funktion wird in C# importiert und von der C#-MethodeGetHRef
aufgerufen. DieGetHRef
-Methode wird später in diesem Abschnitt beschrieben.exports.MyClass.Greeting()
führt Aufrufe in .NET (MyClass.Greeting
) von JS durch. Die C#-MethodeGreeting
gibt eine Zeichenfolge zurück, die das Ergebnis des Aufrufs derwindow.location.href
-Funktion enthält. DieGreeting
-Methode wird später in diesem Abschnitt beschrieben.dotnet.run()
führtProgram.Main
aus.
Modul 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();
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 dom.setInnerText
-Funktion aus dem Modul main.js
aufgerufen, wenn die SetInnerText
-Methode aufgerufen wird:
[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);
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.
Weitere Informationen über die Typzuordnung und Beispiele finden Sie unter JavaScript-Interoperabilität durch `[JSImport]`/`[JSExport]` mit in .NET WebAssembly.
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 wird jede Methode in JS exportiert und kann aus JS-Funktionen aufgerufen werden:
- Die
Toggle
-Methode startet oder unterbricht die Stoppuhr je nach Ausführungsstatus. - Die
Reset
-Methode startet die Stoppuhr neu, wenn sie ausgeführt wird, oder setzt sie zurück, wenn sie nicht ausgeführt wird. - Die
IsRunning
-Methode gibt an, ob die Stoppuhr ausgeführt wird.
[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;
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 8 unterstützt und bieten eine Grundlage für die Verwendung von .NET in WASM von JS.
Die Vorlagen können auch mit dem folgenden Befehl über das NuGet-Paket Microsoft.NET.Runtime.WebAssembly.Templates
installiert werden:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
Browser-App
Sie können mit der Vorlage wasmbrowser
an der Befehlszeile 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
Alternativ können Sie in Visual Studio die App mithilfe der Projektvorlage WebAssembly Browser App erstellen.
Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI:
dotnet build
Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI, und führen Sie sie aus:
dotnet run
Alternativ können Sie den Befehl dotnet serve
installieren und verwenden:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish
Im vorherigen Beispiel ist der Platzhalter {TARGET FRAMEWORK}
der Zielframeworkmoniker.
Node.js-Konsolen-App
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
Alternativ können Sie in Visual Studio die App mithilfe der Projektvorlage WebAssembly Console App erstellen.
Erstellen Sie die App in Visual Studio oder mithilfe der .NET CLI:
dotnet build
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 Ausgabeverzeichnis der Veröffentlichung starten, das die Datei main.mjs
enthält:
node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs
Im vorherigen Beispiel ist der Platzhalter {TARGET FRAMEWORK}
der Zielframeworkmoniker, und der Platzhalter {PATH}
ist der Pfad zur Datei main.mjs
.
Zusätzliche Ressourcen
- JavaScript-Interoperabilität durch `[JSImport]`/`[JSExport]` in .NET WebAssembly
- JavaScript-Interoperabilität durch Import/Export mit ASP.NET Core Blazor.
- API-Dokumentation
- Im GitHub-Repository
dotnet/runtime
: - Verwenden von .NET aus einer beliebigen JavaScript-App in .NET 7