JavaScript [JSImport]
/[JSExport]
Interop 搭配 WebAssembly Browser App 專案
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何使用 JS[JSImport]
/[JSExport]
Interop,設定 WebAssembly Browser App 專案以從 JavaScript (JS) 執行 .NET。 如需其他資訊與範例,請參閱 .NET WebAssembly 中的 JavaScript `[JSImport]`/`[JSExport]` 互通。
如需其他指引,請參閱 .NET 執行階段 (dotnet/runtime
) GitHub 存放庫中的設定和裝載 .NET WebAssembly 應用程式指引。
現有的 JS 應用程式可以使用擴充的用戶端 WebAssembly 支援,重複使用 JS 中的 .NET 程式庫或建置新型以 .NET 為基礎的應用程式和架構。
注意
本文著重於從 JS 應用程式執行 .NET,而不需要依賴 Blazor。 如需在 Blazor WebAssembly 應用程式中使用 [JSImport]
/[JSExport]
互通的指引,請參閱 JavaScript JSImport/JSExport 與 ASP.NET Core Blazor 互通。
當您只預期 Blazor 應用程式在 WebAssembly (WASM) 上執行時,這些是合適的方法。 程式庫可以透過呼叫 OperatingSystem.IsBrowser 進行執行階段檢查,以判斷應用程式是否在 WASM 上執行。
必要條件
在系統管理命令殼層中安裝 wasm-tools
工作負載,這會帶入相關的 MSBuild 目標:
dotnet workload install wasm-tools
這些工具也可以透過 Visual Studio 安裝程式,在 Visual Studio 安裝程式中的 ASP.NET 和網頁程式開發下進行安裝。 從選擇性元件的清單選取 [.NET WebAssembly 建置工具] 選項。
或者,安裝 wasm-experimental
工作負載,新增下列實驗性專案範本:
- WebAssembly Browser App,在瀏覽器應用程式中的 WebAssembly 上開始使用 .NET。
- WebAssembly 主控台應用程式用於在 Node.js 型主控台應用程式中開始使用。
安裝工作負載之後,可以在建立新專案時選取這些新範本。 如果您打算將 JS[JSImport]
/[JSExport]
Interop 整合至現有的 JS 應用程式,則不需要此工作負載。
dotnet workload install wasm-experimental
您也可以使用下列命令,從 Microsoft.NET.Runtime.WebAssembly.Templates
NuGet 套件安裝範本:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
如需詳細資訊,請參閱實驗性工作負載和專案範本一節。
Namespace
本文所述的 JS Interop API 是由 System.Runtime.InteropServices.JavaScript 命名空間中的屬性所控制。
專案組態
若要設定專案 (.csproj
) 以啟用 JS Interop:
設定目標 Framework Moniker (
{TARGET FRAMEWORK}
預留位置):<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
支援 .NET 7 (
net7.0
) 或更新版本。啟用 AllowUnsafeBlocks 屬性,此屬性允許 Roslyn 編譯器中的程式碼產生器使用指標進行 JS Interop:
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
警告
JS Interop API 需要啟用 AllowUnsafeBlocks。 在 .NET 應用程式中實作本身不安全的程式碼時請小心,這可能會帶來安全性和穩定性風險。 如需詳細資訊,請參閱不安全的程式碼、指標型別和函式指標。
下列是設定後的範例專案檔 (.csproj
)。 {TARGET FRAMEWORK}
預留位置是目標架構:
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>{TARGET FRAMEWORK}</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
-
<TargetFramework>net7.0</TargetFramework>
支援 .NET 7 (
net7.0
) 或更新版本。 指定
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
將作為應用程式建置輸出的一部分產生。重要
若要與現有的應用程式整合,請將發佈輸出資料夾† 的內容複製到現有應用程式的部署資產,以便與應用程式的rest一起提供。 對於實際執行環境部署,請在命令殼層中使用
dotnet publish -c Release
命令發佈應用程式,並使用該應用程式部署輸出資料夾的內容。†發佈輸出資料夾是發佈設定檔的目標位置。 .NET 8 或更新版本中的 Release 設定檔預設值為
bin/Release/{TARGET FRAMEWORK}/publish
,其中{TARGET FRAMEWORK}
預留位置是目標架構 (例如net8.0
)。dotnet.create()
設定 .NET WebAssembly 執行階段。
setModuleImports
將名稱與包含 JS 函式的模組相關聯,以便將其匯入 .NET。 JS 模組包含接受的dom.setInnerText
函數,以及元素選取器和顯目 UI 目前 stopwatch 時間。 該模組的名稱可以是任何字串 (不一定要是檔案名稱),但必須與JSImportAttribute
使用的名稱相符 (本文稍後說明)。dom.setInnerText
函式會匯入 C# 中,並由 C# 方法SetInnerText
呼叫。 本節稍後會顯示SetInnerText
方法。exports.StopwatchSample.Reset()
從 JS 呼叫 .NET (StopwatchSample.Reset
)。Reset
C# 方法會在執行時重新啟動 stopwatch,或在未執行時將其重設。 本節稍後會顯示Reset
方法。exports.StopwatchSample.Toggle()
從 JS 呼叫 .NET (StopwatchSample.Toggle
)。Toggle
C# 方法會根據目前是否執行,啟動或停止 stopwatch。 本節稍後會顯示Toggle
方法。runMain()
執行Program.Main
。
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 './_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();
若要匯入 JS 函式,以便從 C# 呼叫函式,請在相符的方法簽章上使用新的 JSImportAttribute。 JSImportAttribute 的第一個參數是要匯入 JS 函式的名稱,而第二個參數是模組的名稱。
在下列範例中,當呼叫 SetInnerText
方法時,會從 main.js
模組呼叫 dom.setInnerText
函式:
[JSImport("dom.setInnerText", "main.js")]
internal static partial void SetInnerText(string selector, string content);
在下列範例中,當呼叫 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 WebAssembly 中的 JavaScript `[JSImport]`/`[JSExport]` 互通。。
您可以藉由在函式名稱中使用 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。
在下列範例中,每個方法都會匯出至 JS,而且可以從 JS 函數呼叫:
Toggle
方法根據執行狀態啟動或停止 stopwatch。Reset
方法會在執行時重新啟動 stopwatch 或在未執行時將其重設。IsRunning
方法表示 stopwatch 是否執行。
[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;
在下列範例中,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 8 中受到支援,並為在 JS 的 WASM 上使用 .NET 提供基礎。
您也可以使用下列命令,從 Microsoft.NET.Runtime.WebAssembly.Templates
NuGet 套件安裝範本:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
瀏覽器應用程式
您可以從命令行使用 wasmbrowser
範本建立瀏覽器應用程式,以建立一個 Web 應用程式,示範如何在瀏覽器中一起使用 .NET 和 JS:
dotnet new wasmbrowser
或者,您也可以在 Visual Studio 中使用 WebAssembly Browser App 專案範本建立應用程式。
從 Visual Studio 或使用 .NET CLI 建置應用程式:
dotnet build
從 Visual Studio 或使用 .NET CLI 建置並執行應用程式:
dotnet run
或者,安裝並使用 dotnet serve
命令:
dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/publish
在上述範例中,{TARGET FRAMEWORK}
預留位置是目標 Framework Moniker。
Node.js 主控台應用程式
您可以使用 wasmconsole
範本建立主控台應用程式,這可以建立一個在 WASM 下執行的應用程式,作為 Node.js 或 V8 主控台應用程式:
dotnet new wasmconsole
或者,您也可以在 Visual Studio 中使用 WebAssembly Console App 專案範本建立應用程式。
從 Visual Studio 或使用 .NET CLI 建置應用程式:
dotnet build
從 Visual Studio 或使用 .NET CLI 建置並執行應用程式:
dotnet run
或者,從包含 main.mjs
檔案的發佈輸出目錄啟動任何靜態檔案伺服器:
node bin/$(Configuration)/{TARGET FRAMEWORK}/{PATH}/main.mjs
在上述範例中,{TARGET FRAMEWORK}
預留位置是目標 Framework Moniker,而 {PATH}
預留位置是 main.mjs
檔案的路徑。
其他資源
- .NET WebAssembly 中的 JavaScript `[JSImport]`/`[JSExport]` 互通
- JavaScript JSImport/JSExport 與 ASP.NET Core Blazor互通
- API 文件
- 在
dotnet/runtime
GitHub 存放庫中: - 從 .NET 7 中的任何 JavaScript 應用程式使用 .NET