從 JavaScript 執行 .NET
本文說明如何使用 JS[JSImport]
/[JSExport]
Interop 從 JavaScript (JS) 使用 .NET。
如需其他指引,請參閱 .NET 執行階段 (dotnet/runtime
) GitHub 存放庫中的設定和裝載 .NET WebAssembly 應用程式指引。 我們計畫在 2023 年或 2024 年初更新本文,在交叉連結指引中包含新的資訊。
現有的 JS 應用程式可以使用 .NET 7 或更新版本中擴充的用戶端 WebAssembly 支援,重複使用 JS 中的 .NET 程式庫或建置新型以 .NET 為基礎的應用程式和架構。
注意
本文著重於從 JS 應用程式執行 .NET,而不需要依賴 Blazor。 如需在 Blazor WebAssembly 應用程式中使用 [JSImport]
/[JSExport]
Interop 的指引,請參閱 JavaScript JSImport/JSExport 與 ASP.NET Core Blazor 互通。
當您只預期在 WebAssembly (WASM) 上執行時,這些方法是合適的。 程式庫可以透過呼叫 OperatingSystem.IsBrowser 進行執行階段檢查,以判斷應用程式是否在 WASM 上執行。
必要條件
安裝 .NET SDK 的最新版本。
安裝 wasm-tools
工作負載,這會帶入相關的 MSBuild 目標。
dotnet workload install wasm-tools
您可以選擇性地安裝 wasm-experimental
工作負載,其中包含在瀏覽器應用程式 (WebAssembly Browser App) 或 Node.js 型主控台應用程式 (WebAssembly Console App) 中在 WebAssembly 上開始使用 .NET 的實驗專案範本。 如果您打算將 JS[JSImport]
/[JSExport]
Interop 整合至現有的 JS 應用程式,則不需要此工作負載。
dotnet workload install wasm-experimental
如需詳細資訊,請參閱實驗性工作負載和專案範本一節。
Namespace
本文所述的 JS Interop API 是由 System.Runtime.InteropServices.JavaScript 命名空間中的屬性所控制。
專案組態
若要設定專案 (.csproj
) 以啟用 JS Interop:
目標
net7.0
或更新版本:<TargetFramework>net7.0</TargetFramework>
指定
browser-wasm
作為執行階段識別碼:<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
指定可執行檔輸出型別:
<OutputType>Exe</OutputType>
啟用 AllowUnsafeBlocks 屬性,此屬性允許 Roslyn 編譯器中的程式碼產生器使用指標進行 JS Interop:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
警告
JS Interop API 需要啟用 AllowUnsafeBlocks。 在 .NET 應用程式中實作本身不安全的程式碼時請小心,這可能會帶來安全性和穩定性風險。 如需詳細資訊,請參閱不安全的程式碼、指標型別和函式指標。
指定
WasmMainJSPath
以指向磁碟上的檔案。 此檔案會隨應用程式一起發佈,但如果您將 .NET 整合到現有 JS 應用程式中,則不需要使用此檔案。在下列範例中,磁碟上的 JS 的檔案是
main.js
,但任何 JS 檔案名稱都是允許的:<WasmMainJSPath>main.js</WasmMainJSPath>
設定後的範例專案檔 (.csproj
):
<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>
WASM 上的 JavaScript interop
下列範例中的 API 是從 dotnet.js
匯入的。 這些 API 讓您能夠設定可匯入 C# 程式碼中的具名模組,並呼叫 .NET 程式碼所公開的方法,包括 Program.Main
。
重要
本文中所定義的「匯入」和「匯出」都是從 .NET 的觀點來看:
- 應用程式匯入 JS 方法,以便從 .NET 呼叫它們。
- 應用程式匯出 .NET 方法,以便從 JS 呼叫它們。
在以下範例中:
dotnet.js
檔案是用來建立和啟動 .NET WebAssembly 執行階段。dotnet.js
會作為應用程式組建輸出的一部分產生,並可在AppBundle
資料夾中找到:bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
{BUILD CONFIGURATION}
預留位置是組建組態 (例如,Debug
、Release
),而{TARGET FRAMEWORK}
預留位置是目標 Framework (例如,net7.0
)。重要
若要與現有的應用程式整合,請複製
AppBundle
資料夾的內容,以便與應用程式的其餘部分一起提供。 對於實際執行環境部署,請在命令殼層中使用dotnet publish -c Release
命令發佈應用程式,並使用該應用程式部署AppBundle
資料夾。dotnet.create()
設定 .NET WebAssembly 執行階段。setModuleImports
將名稱與包含 JS 函式的模組相關聯,以便將其匯入 .NET。 JS 模組包含window.location.href
函式,其會傳回目前的頁面位址 (URL)。 該模組的名稱可以是任何字串 (不一定要是檔案名稱),但必須與JSImportAttribute
使用的名稱相符 (本文稍後說明)。window.location.href
函式會匯入 C# 中,並由 C# 方法GetHRef
呼叫。 本節稍後會顯示GetHRef
方法。exports.MyClass.Greeting()
從 JS 呼叫 .NET (MyClass.Greeting
)。Greeting
C# 方法會傳回一個字串,其中包含呼叫window.location.href
函式的結果。 本節稍後會顯示Greeting
方法。dotnet.run()
執行Program.Main
。
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();
若要匯入 JS 函式,以便從 C# 呼叫函式,請在相符的方法簽章上使用新的 JSImportAttribute。 JSImportAttribute 的第一個參數是要匯入 JS 函式的名稱,而第二個參數是模組的名稱。
在下列範例中,當呼叫 GetHRef
方法時,會從 main.js
模組呼叫 window.location.href
函式:
[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();
在匯入的方法簽章中,您可以使用 .NET 型別作為參數和傳回值,這些值是由執行階段自動封送處理。 使用 JSMarshalAsAttribute<T> 來控制匯入的方法參數如何封送處理。 例如,您可以選擇將 long
封送處理為 System.Runtime.InteropServices.JavaScript.JSType.Number 或 System.Runtime.InteropServices.JavaScript.JSType.BigInt。 您可以將 Action/Func<TResult> 回呼作為參數傳遞,這些回呼會封送為可呼叫的 JS 函式。 您可以傳遞 JS 和受控物件參考,而且它們會封送處理為 Proxy 物件,讓物件在跨邊界保持運作,直到 Proxy 被垃圾回收為止。 您也可以使用 Task 結果匯入和匯出非同步方法,這些方法會按照 JS Promises 進行封送處理。 大部分封送處理的型別在雙向運作中皆可運作,作為參數和傳回值,用於匯入和匯出的方法。
下表指出支援的型別對應。
.NET | JavaScript | Nullable |
Task 至Promise |
JSMarshalAs 選用 |
Array of |
---|---|---|---|---|---|
Boolean |
Boolean |
支援 | 支援 | 支援 | 不支援 |
Byte |
Number |
支援 | 支援 | 支援 | 支援 |
Char |
String |
支援 | 支援 | 支援 | 不支援 |
Int16 |
Number |
支援 | 支援 | 支援 | 不支援 |
Int32 |
Number |
支援 | 支援 | 支援 | 支援 |
Int64 |
Number |
支援 | 支援 | 不支援 | 不支援 |
Int64 |
BigInt |
支援 | 支援 | 不支援 | 不支援 |
Single |
Number |
支援 | 支援 | 支援 | 不支援 |
Double |
Number |
支援 | 支援 | 支援 | 支援 |
IntPtr |
Number |
支援 | 支援 | 支援 | 不支援 |
DateTime |
Date |
支援 | 支援 | 不支援 | 不支援 |
DateTimeOffset |
Date |
支援 | 支援 | 不支援 | 不支援 |
Exception |
Error |
不支援 | 支援 | 支援 | 不支援 |
JSObject |
Object |
不支援 | 支援 | 支援 | 支援 |
String |
String |
不支援 | 支援 | 支援 | 支援 |
Object |
Any |
不支援 | 支援 | 不支援 | 支援 |
Span<Byte> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
Span<Int32> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
Span<Double> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
ArraySegment<Byte> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
ArraySegment<Int32> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
ArraySegment<Double> |
MemoryView |
不支援 | 不支援 | 不支援 | 不支援 |
Task |
Promise |
不支援 | 不支援 | 支援 | 不支援 |
Action |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Action<T1> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Action<T1, T2> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Action<T1, T2, T3> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Func<TResult> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Func<T1, TResult> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Func<T1, T2, TResult> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
Func<T1, T2, T3, TResult> |
Function |
不支援 | 不支援 | 不支援 | 不支援 |
下列條件適用於型別對應和封送處理值:
Array of
資料行會指出 .NET 型別是否可封送處理為 JSArray
。 範例:C#int[]
(Int32
) 對應至 JSNumber
的Array
。- 將 JS 值傳遞至使用錯誤型別值的 C# 時,架構在大部分情況下會擲回例外狀況。 架構不會在 JS 中執行編譯時間型別檢查。
JSObject
、Exception
、Task
和ArraySegment
建立GCHandle
及 Proxy。 您可以在開發人員程式碼中觸發處置,或稍後允許 .NET 記憶體回收 (GC) 處置物件。 這些型別具有顯著的效能額外負荷。Array
:封送處理陣列會在 JS 或 .NET 中建立陣列的複本。MemoryView
MemoryView
是 .NET WebAssembly 執行階段的 JS 類別,用於封送處理Span
和ArraySegment
。- 與封送處理陣列不同,封送處理
Span
或ArraySegment
不會建立基礎記憶體的複本。 MemoryView
只能由 .NET WebAssembly 執行階段正確具現化。 因此,無法將 JS 函式匯入為具有Span
或ArraySegment
參數的 .NET 方法。- 為
Span
建立的MemoryView
只在 Interop 呼叫期間才有效。 如同Span
在呼叫堆疊上配置,在 Interop 呼叫之後不會保存,因此無法匯出傳回Span
的 .NET 方法。 - 針對
ArraySegment
建立的MemoryView
在 Interop 呼叫之後存留下來,而且有助於共用緩衝區。 在針對ArraySegment
建立的MemoryView
上呼叫dispose()
會處置 Proxy,並取消釘選基礎 .NET 陣列。 我們建議在MemoryView
的try-finally
區塊中呼叫dispose()
。
您可以藉由在函式名稱中使用 globalThis
前置詞以及使用 [JSImport]
屬性來匯入可在全域命名空間上可存取的函式,而無須提供模組名稱來匯入。 在下列範例中,console.log
的前置詞為 globalThis
。 匯入的函式是由 C# Log
方法呼叫,該方法接受 C# 字串訊息(message
) 並將 C# 字串封送處理為console.log
的 JSString
:
[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);
若要匯出 .NET 方法,以便從 JS 呼叫它,請使用 JSExportAttribute。
在下列範例中,Greeting
方法會傳回包含呼叫 GetHRef
方法結果的字串。 如先前所示,GetHref
C# 方法會從 main.js
模組針對 window.location.href
函式呼叫 JS。 window.location.href
會傳回目前的頁面位址(URL):
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from {GetHRef()}";
Console.WriteLine(text);
return text;
}
實驗性工作負載和專案範本
若要示範 JS Interop 功能並取得 JS Interop 專案範本,請安裝 wasm-experimental
工作負載:
dotnet workload install wasm-experimental
wasm-experimental
工作負載包含兩個專案範本:wasmbrowser
和 wasmconsole
。 這些範本目前處於實驗階段,這表示範本的開發人員工作流程正在不斷演進。 不過,範本中使用的 .NET 和 JS API 在 .NET 7 中受到支援,並為在 JS 的 WASM 上使用 .NET 提供基礎。
瀏覽器應用程式
您可以使用 wasmbrowser
範本建立瀏覽器應用程式,以建立一個 Web 應用程式,示範如何在瀏覽器中一起使用 .NET 和 JS:
dotnet new wasmbrowser
從 Visual Studio 或使用 .NET CLI 建置應用程式:
dotnet build
建置的應用程式位於 bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
目錄中。 {BUILD CONFIGURATION}
預留位置是組建組態 (例如,Debug
、Release
)。 {TARGET FRAMEWORK}
預留位置是目標 Framework Moniker (例如,net7.0
)。
從 Visual Studio 或使用 .NET CLI 建置並執行應用程式:
dotnet run
或者,從 AppBundle
目錄啟動任何靜態檔案伺服器:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle
在上述範例中,{TARGET FRAMEWORK}
預留位置是目標 Framework Moniker (例如,net7.0
)。
Node.js 主控台應用程式
您可以使用 wasmconsole
範本建立主控台應用程式,以建立在 WASM 下作為 Node.js 或 V8 主控台應用程式執行的應用程式:
dotnet new wasmconsole
從 Visual Studio 或使用 .NET CLI 建置應用程式:
dotnet build
建置的應用程式位於 bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle
目錄中。 {BUILD CONFIGURATION}
預留位置是組建組態 (例如,Debug
、Release
)。 {TARGET FRAMEWORK}
預留位置是目標 Framework Moniker (例如,net7.0
)。
從 Visual Studio 或使用 .NET CLI 建置並執行應用程式:
dotnet run
或者,從 AppBundle
目錄啟動任何靜態檔案伺服器:
node bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle/main.mjs
在上述範例中,{TARGET FRAMEWORK}
預留位置是目標 Framework Moniker (例如,net7.0
)。
其他資源
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應