JavaScript [JSImport]
/[JSExport]
Interop 搭配 ASP.NET Core Blazor
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
本文說明如何使用 JavaScript (JS) [JSImport]
/[JSExport]
Interop API,與用戶端元件中的 JavaScript (JS) 互動。 如需其他資訊與範例,請參閱 在 .NET WebAssembly 中的 JavaScript `[JSImport]`/`[JSExport]` 互通。
如需其他指引,請參閱 .NET 執行階段 (dotnet/runtime
) GitHub 存放庫中的設定和裝載 .NET WebAssembly 應用程式指引。
Blazor 會根據 IJSRuntime 介面提供自己的 JS Interop 機制。 Blazor 跨轉譯模式和 Blazor Hybrid 應用程式都一致支援 Blazor 的 JS Interop。 IJSRuntime 也可讓程式庫作者建置可跨 Blazor 生態系統共用的 JS Interop 程式庫,並且仍然是 Blazor 中 JS Interop 的建議方法。 請參閱以下文章:
本文描述在 WebAssembly 上執行的用戶端元件特定的替代 JS Interop 方法。 當您只預期在用戶端 WebAssembly 上執行時,這些方法就很合適。 程式庫作者可以使用這些方法來最佳化 JS Interop,方法是在程式碼執行期間檢查應用程式是否在瀏覽器 (OperatingSystem.IsBrowser) 中的 WebAssembly 上執行。 本文所述的方法應該用來在移轉至 .NET 7 或更新版本時取代過時的解除封送 JS Interop API。
注意
本文著重於用戶端元件中的 JS Interop。 如需在 JavaScript 應用程式中呼叫 .NET 的指引,請參閱 JavaScript `[JSImport]`/`[JSExport]` 與 WebAssembly 瀏覽器應用程式專案互通。
過時的 JavaScript Interop API
使用 IJSUnmarshalledRuntime API 解除封送的 JS Interop 在 .NET 7 或更新版本的 ASP.NET Core 中已淘汰。 遵循本文中的指導來取代過時的 API。
必要條件
Visual Studio 搭配 ASP.NET 與網頁程式開發工作負載。
如果您規劃要在從 Blazor WebAssembly 專案範本產生的 Blazor WebAssembly 應用程式中實作 [JSImport]
/[JSExport]
Interop,就不需要進一步的工具。
如果您規劃要使用 WebAssembly Browser 或 WebAssembly Console App 專案範本,請使用下列命令安裝 Microsoft.NET.Runtime.WebAssembly.Templates
NuGet 套件:
dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates
如需詳細資訊,請參閱 JavaScript `[JSImport]`/`[JSExport]` 與 WebAssembly 瀏覽器應用程式專案互通。
Namespace
本文所述的 JS Interop API (JSHost.ImportAsync) 是由 System.Runtime.InteropServices.JavaScript 命名空間中的屬性所控制。
啟用不安全的區塊
啟用應用程式專案檔中的 AllowUnsafeBlocks 屬性,其會允許 Roslyn 編譯器中的程式碼產生器使用指標進行 JS Interop:
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
警告
JS Interop API 需要啟用 AllowUnsafeBlocks。 在 .NET 應用程式中實作本身不安全的程式碼時請小心,這可能會帶來安全性和穩定性風險。 如需詳細資訊,請參閱不安全的程式碼、指標型別和函式指標。
不支援 Razor 類別庫 (RCL) 共置的 JS
一般而言,對於以 IJSRuntime 為基礎的 JS Interop,其 JS 位置支援 (ASP.NET Core Blazor 應用程式中的 JavaScript 位置) 也會為本文中描述的 [JSImport]
/[JSExport]
顯示。 唯一不支援的 JS 位置功能是用於 Razor 類別庫 (RCL) 中共置的 JS 時。
請不要在 RCL 中使用共置的 JS,而是將 JS 檔案放在 RCL 的 wwwroot
資料夾中,並使用 RCL 靜態資產的一般路徑加以參考:
_content/{PACKAGE ID}/{PATH}/{FILE NAME}.js
{PACKAGE ID}
預留位置是 RCL 的封裝識別碼 (或類別庫的庫名)。{PATH}
預留位置是檔案的路徑。{FILE NAME}
預留位置是檔案名稱。
[JSImport]
/[JSExport]
Interop 不支援 RCL 中共置的 JS,但您可以採用下列其中一種方法或兩種方法來使您的 JS 檔案井然有序:
- 將 JS 檔案命名為使用 JS 的元件名稱。 針對名為
CallJavaScriptFromLib
(CallJavaScriptFromLib.razor
) 之 RCL 中的元件,命名wwwroot
資料夾中的CallJavaScriptFromLib.js
檔案。 - 將元件專屬的 JS 檔案放在 RCL
wwwroot
資料夾內的Components
資料夾中,並在檔案的路徑中使用 "Components
":_content/{PACKAGE ID}/Components/CallJavaScriptFromLib.js
。
從 .NET 呼叫 JavaScript
本節說明如何從 .NET 呼叫 JS 函式。
在下列 CallJavaScript1
元件中:
CallJavaScript1
模組會以非同步方式從共置的 JS 檔案使用 JSHost.ImportAsync 匯入。- 匯入的
getMessage
JS 函式是由GetWelcomeMessage
呼叫。 - 傳回的歡迎訊息字串會透過
message
欄位顯示在 UI 中。
CallJavaScript1.razor
:
@page "/call-javascript-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 1)
</h1>
@(message is not null ? message : string.Empty)
@code {
private string? message;
protected override async Task OnInitializedAsync()
{
await JSHost.ImportAsync("CallJavaScript1",
"../Components/Pages/CallJavaScript1.razor.js");
message = GetWelcomeMessage();
}
}
@page "/call-javascript-1"
@using System.Runtime.InteropServices.JavaScript
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 1)
</h1>
@(message is not null ? message : string.Empty)
@code {
private string? message;
protected override async Task OnInitializedAsync()
{
await JSHost.ImportAsync("CallJavaScript1",
"../Pages/CallJavaScript1.razor.js");
message = GetWelcomeMessage();
}
}
注意
使用 OperatingSystem.IsBrowser 在程式碼中包含條件式簽入,以確保 JS Interop 只會由用戶端上轉譯的元件呼叫。 對於以伺服器端元件為目標的程式庫/NuGet 封裝 (其無法執行此 JS Interop API 所提供的程式碼),這很重要。
若要匯入 JS 函式以從 C# 呼叫它,請在符合 JS 函式簽章的 C# 方法簽章上使用 [JSImport]
屬性。 [JSImport]
屬性的第一個參數是要匯入 JS 函式的名稱,而第二個參數是 JS 模組的名稱。
在下列範例中,getMessage
是針對名為 CallJavaScript1
的模組傳回 string
的 JS 函式。 C# 方法簽章相符:不會將任何參數傳遞至 JS 函式,而 JS 函式會傳回 string
。 JS 函式是由 GetWelcomeMessage
在 C# 程式碼中呼叫。
CallJavaScript1.razor.cs
:
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
namespace BlazorSample.Components.Pages;
[SupportedOSPlatform("browser")]
public partial class CallJavaScript1
{
[JSImport("getMessage", "CallJavaScript1")]
internal static partial string GetWelcomeMessage();
}
上述 CallJavaScript1
部分類別的應用程式命名空間為 BlazorSample
。 元件的命名空間為 BlazorSample.Components.Pages
。 如果在本機測試應用程式中使用上述元件,請更新命名空間以符合應用程式。 例如,如果應用程式的命名空間是 ContosoApp
,則命名空間為 ContosoApp.Components.Pages
。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
namespace BlazorSample.Pages;
[SupportedOSPlatform("browser")]
public partial class CallJavaScript1
{
[JSImport("getMessage", "CallJavaScript1")]
internal static partial string GetWelcomeMessage();
}
上述 CallJavaScript1
部分類別的應用程式命名空間為 BlazorSample
。 元件的命名空間為 BlazorSample.Pages
。 如果在本機測試應用程式中使用上述元件,請更新命名空間以符合應用程式。 例如,如果應用程式的命名空間是 ContosoApp
,則命名空間為 ContosoApp.Pages
。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。
在匯入的方法簽章中,您可以使用 .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 進行封送處理。 大部分封送處理型別可以以參數和傳回值形式,在匯入和匯出方法上雙向運作,本文稍後的從 JavaScript 呼叫 .NET 小節將會說明。
如需其他類型對應資訊與範例,請參閱 .NET WebAssembly 中的 JavaScript `[JSImport]`/`[JSExport]` 互通。
[JSImport]
屬性中的模組名稱,以及使用 JSHost.ImportAsync 在元件中載入模組的呼叫必須相符且在應用程式中是唯一的。 撰寫程式庫以在 NuGet 封裝中部署時,建議您使用 NuGet 封裝命名空間作為模組名稱的前置詞。 在下列範例中,模組名稱會反映 Contoso.InteropServices.JavaScript
封裝和使用者訊息 Interop 類別的資料夾 (UserMessages
):
[JSImport("getMessage",
"Contoso.InteropServices.JavaScript.UserMessages.CallJavaScript1")]
可在全域命名空間上存取的函式,可以在函式名稱中使用 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);
從標準 JavaScript 模組匯出指令碼,以與元件共置,或與其他 JavaScript 靜態資產放置在 JS 檔案中 (例如,wwwroot/js/{FILE NAME}.js
,其中的 JS 靜態資產會保留在應用程式 wwwroot
資料夾中名為 js
的資料夾中,而 {FILE NAME}
預留位置是檔案名稱)。
在下列範例中,名為 getMessage
的 JS 函式會從共置的 JS 檔案匯出,其會傳回葡萄牙文的歡迎訊息 "Hello from Blazor!":
CallJavaScript1.razor.js
:
export function getMessage() {
return 'Olá do Blazor!';
}
從 JavaScript 呼叫 .NET
本節說明如何從 JS 呼叫 .NET 方法。
下列 CallDotNet1
元件會呼叫直接與 DOM 互動以轉譯歡迎訊息字串的 JS:
CallDotNet
JS 模組會從此元件的共置 JS 檔案非同步匯入。- 匯入的
setMessage
JS 函式是由SetWelcomeMessage
呼叫。 - 傳回的歡迎訊息字串會由
setMessage
透過message
欄位顯示在 UI 中。
重要
在本節的範例中,JS Interop 是用來在 OnAfterRender
中轉譯元件之後,純粹為了示範目的而變動 DOM 元素。 一般而言,您只應該在物件未與 Blazor 互動時使用 JS 來變動 DOM。 本節中顯示的方法與 Razor 元件中使用的第三方 JS 程式庫的情況類似,其中元件會透過 JS Interop 與 JS 程式庫互動,第三方 JS 程式庫會與 DOM 的一部分互動,而 Blazor 不會直接與對 DOM 部分的 DOM 更新相關。 如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
CallDotNet1.razor
:
@page "/call-dotnet-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 1)
</h1>
<p>
<span id="result">.NET method not executed yet</span>
</p>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSHost.ImportAsync("CallDotNet1",
"../Components/Pages/CallDotNet1.razor.js");
SetWelcomeMessage();
}
}
}
@page "/call-dotnet-1"
@using System.Runtime.InteropServices.JavaScript
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 1)
</h1>
<p>
<span id="result">.NET method not executed yet</span>
</p>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSHost.ImportAsync("CallDotNet1",
"../Pages/CallDotNet1.razor.js");
SetWelcomeMessage();
}
}
}
若要匯出 .NET 方法,以便從 JS 呼叫它,請使用 [JSExport]
屬性。
在以下範例中:
SetWelcomeMessage
會呼叫名為setMessage
的 JS 函式。 JS 函式會呼叫 .NET,以接收來自GetMessageFromDotnet
的歡迎訊息,並在 UI 中顯示訊息。GetMessageFromDotnet
是 .NET 方法,其[JSExport]
屬性會以葡萄牙文傳回歡迎訊息 "Hello from Blazor!"。
CallDotNet1.razor.cs
:
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
namespace BlazorSample.Components.Pages;
[SupportedOSPlatform("browser")]
public partial class CallDotNet1
{
[JSImport("setMessage", "CallDotNet1")]
internal static partial void SetWelcomeMessage();
[JSExport]
internal static string GetMessageFromDotnet() => "Olá do Blazor!";
}
上述 CallDotNet1
部分類別的應用程式命名空間為 BlazorSample
。 元件的命名空間為 BlazorSample.Components.Pages
。 如果在本機測試應用程式中使用上述元件,請更新應用程式命名空間以符合應用程式。 例如,如果應用程式的命名空間是 ContosoApp
,則元件命名空間為 ContosoApp.Components.Pages
。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。
在下列範例中,名為 setMessage
的 JS 函式會從共置的 JS 檔案匯入。
setMessage
方法:
- 呼叫
globalThis.getDotnetRuntime(0)
以公開 WebAssembly .NET 執行階段執行個體,以呼叫匯出的 .NET 方法。 - 取得應用程式元件的 JS 匯出。 下列範例中應用程式元件的名稱為
BlazorSample
。 - 從匯出 (
exports
) 呼叫BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet
方法。 傳回的值,即歡迎訊息,會指派給CallDotNet1
元件的<span>
文字。 應用程式的命名空間為BlazorSample
,而CallDotNet1
元件的命名空間為BlazorSample.Components.Pages
。
CallDotNet1.razor.js
:
export async function setMessage() {
const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
var exports = await getAssemblyExports("BlazorSample.dll");
document.getElementById("result").innerText =
exports.BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet();
}
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
namespace BlazorSample.Pages;
[SupportedOSPlatform("browser")]
public partial class CallDotNet1
{
[JSImport("setMessage", "CallDotNet1")]
internal static partial void SetWelcomeMessage();
[JSExport]
internal static string GetMessageFromDotnet() => "Olá do Blazor!";
}
上述 CallDotNet1
部分類別的應用程式命名空間為 BlazorSample
。 元件的命名空間為 BlazorSample.Pages
。 如果在本機測試應用程式中使用上述元件,請更新應用程式命名空間以符合應用程式。 例如,如果應用程式的命名空間是 ContosoApp
,則元件命名空間為 ContosoApp.Pages
。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件。
在下列範例中,名為 setMessage
的 JS 函式會從共置的 JS 檔案匯入。
setMessage
方法:
- 呼叫
globalThis.getDotnetRuntime(0)
以公開 WebAssembly .NET 執行階段執行個體,以呼叫匯出的 .NET 方法。 - 取得應用程式元件的 JS 匯出。 下列範例中應用程式元件的名稱為
BlazorSample
。 - 從匯出 (
exports
) 呼叫BlazorSample.Pages.CallDotNet1.GetMessageFromDotnet
方法。 傳回的值,即歡迎訊息,會指派給CallDotNet1
元件的<span>
文字。 應用程式的命名空間為BlazorSample
,而CallDotNet1
元件的命名空間為BlazorSample.Pages
。
CallDotNet1.razor.js
:
export async function setMessage() {
const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
var exports = await getAssemblyExports("BlazorSample.dll");
document.getElementById("result").innerText =
exports.BlazorSample.Pages.CallDotNet1.GetMessageFromDotnet();
}
注意
呼叫 getAssemblyExports
以取得匯出可能發生在 JavaScript 初始化運算式中,以取得整個應用程式的可用性。
多個模組匯入呼叫
載入 JS 模組之後,只要應用程式在瀏覽器視窗或索引標籤中執行,模組的 JS 函式就可供應用程式的元件和類別使用,而不需要使用者手動重新載入應用程式。 JSHost.ImportAsync 可以在相同的模組上呼叫多次,而不會在下列情況下大幅降低效能:
- 使用者會瀏覽呼叫 JSHost.ImportAsync 以匯入模組的元件、導覽離開元件,然後返回元件,在其中會再次呼叫 JSHost.ImportAsync 以取得相同模組匯入。
- 相同模組會由不同的元件使用,並由 JSHost.ImportAsync 在每個元件中載入。
跨元件使用單一 JavaScript 模組
遵循本節中的指導之前,請閱讀本文的從 .NET 呼叫 JavaScript 和從 JavaScript 呼叫 .NET 小節,其中提供有關 [JSImport]
/[JSExport]
Interop 的一般指導。
本節中的範例示範如何從用戶端應用程式中的共用 JS 模組使用 JS Interop。 本節中的指導不適用 Razor 類別庫 (RCL)。
使用下列元件、類別、C# 方法和 JS 函式:
Interop
類別 (Interop.cs
):使用名為Interop
的模組的[JSImport]
和[JSExport]
屬性,設定匯入和匯出 JS Interop。GetWelcomeMessage
:呼叫匯入的getMessage
JS 函式的 .NET 方法。SetWelcomeMessage
:呼叫匯入的setMessage
JS 函式的 .NET 方法。GetMessageFromDotnet
:匯出的 C# 方法,會在從 JS 呼叫時傳回歡迎訊息字串。
wwwroot/js/interop.js
檔案:包含 JS 函式。getMessage
:在元件中由 C# 程式碼呼叫時傳回歡迎訊息。setMessage
:呼叫GetMessageFromDotnet
C# 方法,並將傳回的歡迎訊息指派給 DOM<span>
元素。
Program.cs
呼叫 JSHost.ImportAsync 以從wwwroot/js/interop.js
載入模組。CallJavaScript2
元件 (CallJavaScript2.razor
):在元件的 UI 中呼叫GetWelcomeMessage
並顯示傳回的歡迎訊息。CallDotNet2
元件 (CallDotNet2.razor
):呼叫SetWelcomeMessage
。
Interop.cs
:
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
namespace BlazorSample.JavaScriptInterop;
[SupportedOSPlatform("browser")]
public partial class Interop
{
[JSImport("getMessage", "Interop")]
internal static partial string GetWelcomeMessage();
[JSImport("setMessage", "Interop")]
internal static partial void SetWelcomeMessage();
[JSExport]
internal static string GetMessageFromDotnet() => "Olá do Blazor!";
}
在上述範例中,應用程式的命名空間是 BlazorSample
,而 C# Interop 類別的完整命名空間為 BlazorSample.JavaScriptInterop
。
wwwroot/js/interop.js
:
export function getMessage() {
return 'Olá do Blazor!';
}
export async function setMessage() {
const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
var exports = await getAssemblyExports("BlazorSample.dll");
document.getElementById("result").innerText =
exports.BlazorSample.JavaScriptInterop.Interop.GetMessageFromDotnet();
}
讓 System.Runtime.InteropServices.JavaScript 命名空間在 Program.cs
檔案頂端提供:
using System.Runtime.InteropServices.JavaScript;
在呼叫 WebAssemblyHost.RunAsync 之前在 Program.cs
中載入模組:
if (OperatingSystem.IsBrowser())
{
await JSHost.ImportAsync("Interop", "../js/interop.js");
}
CallJavaScript2.razor
:
@page "/call-javascript-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 2)
</h1>
@(message is not null ? message : string.Empty)
@code {
private string? message;
protected override void OnInitialized()
{
message = Interop.GetWelcomeMessage();
}
}
@page "/call-javascript-2"
@using BlazorSample.JavaScriptInterop
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 2)
</h1>
@(message is not null ? message : string.Empty)
@code {
private string? message;
protected override void OnInitialized()
{
message = Interop.GetWelcomeMessage();
}
}
CallDotNet2.razor
:
@page "/call-dotnet-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 2)
</h1>
<p>
<span id="result">.NET method not executed</span>
</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Interop.SetWelcomeMessage();
}
}
}
@page "/call-dotnet-2"
@using BlazorSample.JavaScriptInterop
<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 2)
</h1>
<p>
<span id="result">.NET method not executed</span>
</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Interop.SetWelcomeMessage();
}
}
}
重要
在本節的範例中,JS Interop 是用來在 OnAfterRender
中轉譯元件之後,純粹為了示範目的而變動 DOM 元素。 一般而言,您只應該在物件未與 Blazor 互動時使用 JS 來變動 DOM。 本節中顯示的方法與 Razor 元件中使用的第三方 JS 程式庫的情況類似,其中元件會透過 JS Interop 與 JS 程式庫互動,第三方 JS 程式庫會與 DOM 的一部分互動,而 Blazor 不會直接與對 DOM 部分的 DOM 更新相關。 如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。