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
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 OrdnerAppBundle
: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 Befehldotnet publish -c Release
in einer Befehlsshell, und stellen Sie den OrdnerAppBundle
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 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 './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
vonNumber
. - 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
undArraySegment
erstellenGCHandle
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 vonSpan
undArraySegment
.- Im Gegensatz zum Marshallen eines Arrays wird beim Marshallen einer
Span
oder einesArraySegment
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 ParameterSpan
oderArraySegment
verfügt.- Wenn
MemoryView
für einenSpan
erstellt wird, gilt sie nur für die Dauer des Interoperabilitätsaufrufs. DaSpan
der Aufrufliste zugewiesen wird, die nach dem Interoperabilitätsaufruf nicht beibehalten wird, ist es nicht möglich, eine .NET-Methode zu exportieren, die einenSpan
zurückgibt. MemoryView
wird für einArraySegment
erstellt und auch nach dem Interoperabilitätsaufruf beibehalten und ist damit für die gemeinsame Verwendung eines Puffers nützlich. Durch Aufrufen vondispose()
in einerMemoryView
, die für einArraySegment
erstellt wurde, wird der Proxy verworfen und das zugrunde liegende .NET-Array getrennt. Es wird empfohlen,dispose()
in einemtry-finally
-Block fürMemoryView
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
- Konfigurieren und Hosten von .NET WebAssembly-Anwendungen
- API-Dokumentation
- JavaScript-Interoperabilität durch JSImport/JSExport mit ASP.NET Core Blazor
- Im GitHub-Repository
dotnet/runtime
: - Verwenden von .NET aus einer beliebigen JavaScript-App in .NET 7
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für