共用方式為


JavaScript [JSImport]/[JSExport] Interop 搭配 WebAssembly Browser App 專案

注意

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前的版本,請參閱 本文的 .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 上執行。

必要條件

.NET SDK (最新版本)

在系統管理命令殼層中安裝 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>
  • 設定目標 Framework Moniker

    <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# 呼叫函式,請在相符的方法簽章上使用新的 JSImportAttributeJSImportAttribute 的第一個參數是要匯入 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.NumberSystem.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 工作負載包含兩個專案範本:wasmbrowserwasmconsole。 這些範本目前處於實驗階段,這表示範本的開發人員工作流程正在不斷演進。 不過,範本中使用的 .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.jsV8 主控台應用程式:

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 檔案的路徑。

其他資源