本文介绍了如何使用 JavaScript (JS)
[JSImport]
/
[JSExport]
互操作 API(针对采用 .NET 7 或更高版本的应用发布的)在 Blazor WebAssembly 应用中与 JavaScript (JS) 交互。
Blazor 基于
IJSRuntime
接口提供自己的 JS 互操作机制,该机制在 Blazor 托管模型中得到统一支持,并在以下文章中进行了介绍:
从 ASP.NET Core
中的 .NET 方法调用 JavaScript 函数
从 ASP.NET Core
中的 JavaScript 函数调用 .NET 方法
IJSRuntime
使库作者能够生成可在 Blazor 生态系统中共享的 JS 互操作库,并且仍然是 Blazor 中建议的 JS 互操作方法。
本文介绍了一种替代的 JS 互操作方法,该方法特定于基于 WebAssembly 的应用,后者在 .NET 7 的发行版中首次提供。 如果只希望在客户端 WebAssembly 上而不是在其他 Blazor 托管模型中运行,这些方法非常合适。 库作者可以使用这些方法优化 JS 互操作,方法是在运行时检查应用是否在浏览器中的 WebAssembly 上运行 (
OperatingSystem.IsBrowser
)。 迁移到 .NET 7 或更高版本时,应使用本文中描述的方法替换已过时的拆收 JS 互操作 API。
本文重点介绍 JS 应用中的 Blazor WebAssembly 互操作。 有关在 JavaScript 应用中调用 .NET 的指南,请参阅
从 JavaScript 运行 .NET
。
已过时的 JavaScript 互操作 API
使用
IJSUnmarshalledRuntime
API 的拆收 JS 互操作在 ASP.NET Core 7.0 或更高版本中已过时。 按照本文中的指导替换已过时的 API。
下载并安装 .NET 7.0 或更高版本
(如果尚且在系统上安装它,或者系统未安装最新版本)。
本文中描述的 JS 互操作 API 由
System.Runtime.InteropServices.JavaScript
命名空间中的属性控制。
启用不安全的块
在应用的项目文件中启用
AllowUnsafeBlocks
属性,这允许 Roslyn 编译器中的代码生成器将指针用于 JS 互操作:
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
JS 互操作 API 需要启用 AllowUnsafeBlocks。 在 .NET 应用中实现自己的不安全代码时要小心,这可能会带来安全和稳定性风险。 有关详细信息,请参阅不安全的代码、指针类型和函数指针。
从 .NET 调用 JavaScript
本节说明如何从 .NET 调用 JS 函数。
在下面的 CallJavaScript1
组件中:
CallJavaScript1
模块是从具有 JSHost.ImportAsync 的并置 JS 文件 异步导入的。
导入的 getMessage
JS 函数由 GetWelcomeMessage
调用。
返回的欢迎消息字符串通过 message
字段显示在 UI 中。
Pages/CallJavaScript1.razor
:
@page "/call-javascript-1"
@using System.Runtime.InteropServices.JavaScript
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 1)
@(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 互操作仅在浏览器的客户端上运行的 Blazor WebAssembly 应用中调用。 这对于以 Blazor 托管不基于 WebAssembly 的模型(例如 Blazor Server 和 Blazor Hybrid)为目标的库/NuGet 包很重要,这些模型无法执行此 JS 互操作 API 提供的代码。
若要导入 JS 函数以从 C# 调用它,请使用与 JS 函数签名匹配的 C# 方法签名上的 [JSImport]
属性。 [JSImport]
属性的第一个参数是要导入的 JS 函数的名称,第二个参数是 JS 模块 的名称。
在以下示例中,getMessage
是一个 JS 函数,它为名为 CallJavaScript1
的模块返回 string
。 C# 方法签名匹配:没有参数传递给 JS 函数,JS 函数返回 string
。 JS 函数由 C# 代码中的 GetWelcomeMessage
调用。
Pages/CallJavaScript1.razor.cs
:
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 组件。
在导入的方法签名中,可以将 .NET 类型用于参数和返回值,它们由运行时自动封送。 使用 JSMarshalAsAttribute<T> 控制导入的方法参数的封送方式。 例如,可以选择将 long
封送为 System.Runtime.InteropServices.JavaScript.JSType.Number 或 System.Runtime.InteropServices.JavaScript.JSType.BigInt。 可以将 Action/Func<TResult> 回调作为参数传递,这些回调被封送为可调用的 JS 函数。 可以同时传递 JS 和托管对象引用,它们被封送为代理对象,使对象在边界上保持活动状态,直到代理被垃圾回收。 还可以导入和导出具有 Task 结果的异步方法,这些结果被封送为 JS 承诺。 大多数封送类型在导入和导出方法上作为参数和返回值双向工作,本文后面的从 JavaScript 调用 .NET 部分对此进行了介绍。
下表指示受支持的类型映射。
JavaScript
Nullable
Task
➔到Promise
JSMarshalAs
可选
Array of
以下条件适用于类型映射和封送的值:
Array of
列指示是否可以将 .NET 类型封送为 JSArray
。 示例:C# int[]
(Int32
) 映射为 Number
的 JSArray
。
将 JS 值传递给具有错误类型的值的 C# 时,在大多数情况下,框架将引发异常。 框架不会在 JS 中执行编译时类型检查。
JSObject
、Exception
、Task
和 ArraySegment
将创建 GCHandle
和代理。 你可以在开发人员代码中触发处置,或允许 .NET 垃圾回收 (GC) 来稍后处置对象。 这些类型会产生显著的性能开销。
Array
:封送数组会在 JS 或 .NET 中创建数组的副本。
MemoryView
MemoryView
是 .NET WebAssembly 运行时用于封送 Span
和 ArraySegment
的 JS 类。
与封送数组不同,封送 Span
或 ArraySegment
不会创建基础内存的副本。
MemoryView
只能由 .NET WebAssembly 运行时正确实例化。 因此,不能将 JS 函数导入为具有 Span
或 ArraySegment
参数的 .NET 方法。
为 Span
创建的 MemoryView
仅在互操作调用期间有效。 由于在调用堆栈上分配了 Span
,这在互操作调用之后不会保留,因此不能导出返回 Span
的 .NET 方法。
为 ArraySegment
创建的 MemoryView
会在互操作调用后存活,并且可用于共享缓冲区。 对为 ArraySegment
创建的 MemoryView
调用 dispose()
将处置代理并将取消固定基础 .NET 数组。 建议在 try-finally
块中为 MemoryView
调用 dispose()
。
以下条件适用于类型映射和封送的值:
Array of
列指示是否可以将 .NET 类型封送为 JSArray
。 示例:C# int[]
(Int32
) 映射为 Number
的 JSArray
。
将 JS 值传递给具有错误类型的值的 C# 时,在大多数情况下,框架将引发异常。 框架不会在 JS 中执行编译时类型检查。
JSObject
、Exception
、Task
和 ArraySegment
将创建 GCHandle
和代理。 你可以在开发人员代码中触发处置,或允许 .NET 垃圾回收 (GC) 来稍后处置对象。 这些类型会产生显著的性能开销。
Array
:封送数组会在 JS 或 .NET 中创建数组的副本。
MemoryView
MemoryView
是 .NET WebAssembly 运行时用于封送 Span
和 ArraySegment
的 JS 类。
与封送数组不同,封送 Span
或 ArraySegment
不会创建基础内存的副本。
MemoryView
只能由 .NET WebAssembly 运行时正确实例化。 因此,不能将 JS 函数导入为具有 Span
或 ArraySegment
参数的 .NET 方法。
为 Span
创建的 MemoryView
仅在互操作调用期间有效。 由于在调用堆栈上分配了 Span
,这在互操作调用之后不会保留,因此不能导出返回 Span
的 .NET 方法。
为 ArraySegment
创建的 MemoryView
会在互操作调用后存活,并且可用于共享缓冲区。 对为 ArraySegment
创建的 MemoryView
调用 dispose()
将处置代理并将取消固定基础 .NET 数组。 建议在 try-finally
块中为 MemoryView
调用 dispose()
。
[JSImport]
属性中的模块名称和在具有 JSHost.ImportAsync 的组件中加载模块的调用必须匹配并且在应用中必须唯一。 为 NuGet 包中的部署创作库时,建议使用 NuGet 包命名空间作为模块名称的前缀。 在以下示例中,模块名称反映了 Contoso.InteropServices.JavaScript
包和用户消息互操作类 (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 ES6 模块中导出脚本,该脚本可以与组件并置,也可以与其他 JavaScript 静态资源一起放置在 JS 文件中(例如 wwwroot/js/{FILE NAME}.js
,其中 JS 静态资产保存在应用的 wwwroot
文件夹中名为 js
的文件夹中,{FILE NAME}
占位符是文件名)。
在以下示例中,名为 getMessage
的 JS 函数从并置的 JS 文件中导出,该文件返回使用葡萄牙语的欢迎消息“Hello from Blazor!”:
Pages/CallJavaScript1.razor.js
:
export function getMessage() {
return 'Olá do Blazor!';
从 JavaScript 调用 .NET
本节说明如何从 JS 调用 .NET 方法。
以下 CallDotNet1
组件调用 JS 直接与 DOM 交互以呈现欢迎消息字符串:
CallDotNet
JS 模块 是从该组件的并置 JS 文件异步导入的。
导入的 setMessage
JS 函数由 SetWelcomeMessage
调用。
返回的欢迎消息由 setMessage
通过 message
字段显示在 UI 中。
在本节的示例中,JS 互操作用于在 OnAfterRender
中呈现组件后改变 DOM 元素(纯粹出于演示目的)。 通常,当对象不与 Blazor 交互时,应只使用 JS 改变 DOM。 本节中显示的方法类似于在 Razor 组件中使用第三方 JS 库的情况,其中组件通过 JS 互操作与 JS 库交互,第三方 JS 库与 DOM 的一部分进行交互,并且 Blazor 不直接参与对 DOM 的该部分的 DOM 更新。 有关详细信息,请参阅 ASP.NET Core JavaScript 互操作性(JS 互操作)。
Pages/CallDotNet1.razor
:
@page "/call-dotnet-1"
@using System.Runtime.InteropServices.JavaScript
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 1)
<span id="result">.NET method not executed yet</span>
@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
是具有 [JSExport]
属性的 .NET 方法,它返回使用葡萄牙语的欢迎消息“Hello from Blazor!”。
Pages/CallDotNet1.razor.cs
:
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()
return "Olá do Blazor!";
上述 CallDotNet1
分部类的应用命名空间是 BlazorSample
。 组件的命名空间为 BlazorSample.Pages
。 如果在本地测试应用中使用上述组件,请更新应用的命名空间以匹配应用。 例如,如果应用的命名空间是 ContosoApp
,则组件命名空间是 ContosoApp.Pages
。 有关详细信息,请参阅 ASP.NET Core 组件。
在以下示例中,名为 setMessage
的 JS 函数是从并置的 JS 文件中导入的。
setMessage
方法:
调用 globalThis.getDotnetRuntime(0)
以公开用于调用导出的 .NET 方法的 WebAssembly .NET 运行时实例。
获取应用程序集的 JS 导出。 以下示例中应用程序集的名称为 BlazorSample
。
从导出 (exports
) 调用 BlazorSample.Pages.CallDotNet1.GetMessageFromDotnet
方法。 返回值(即欢迎消息)分配给 CallDotNet1
组件的 <span>
文本。 应用的命名空间是 BlazorSample
,CallDotNet1
组件的命名空间是 BlazorSample.Pages
。
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]
互操作的指导。
本部分中的示例说明如何在 Blazor WebAssembly 应用中使用来自共享 JS 模块的 JS 互操作。 本部分中的指南不适用于 Razor 类库 (RCL)。
使用了以下组件、类、C# 方法和 JS 函数:
Interop
类 (Interop.cs
):为名为 Interop
的模块设置导入和导出与 [JSImport]
和 [JSExport]
属性的 JS 互操作。
GetWelcomeMessage
:调用导入的 getMessage
JS 函数的 .NET 方法。
SetWelcomeMessage
:调用导入的 setMessage
JS 函数的 .NET 方法。
GetMessageFromDotnet
:从 JS 调用时返回欢迎消息字符串的导出 C# 方法。
wwwroot/js/interop.js
文件:包含 JS 函数。
getMessage
:在组件中被 C# 代码调用时返回欢迎消息。
setMessage
:调用 GetMessageFromDotnet
C# 方法并将返回的欢迎消息分配给 DOM <span>
元素。
Program.cs
调用 JSHost.ImportAsync 以从 wwwroot/js/interop.js
加载模块。
CallJavaScript2
组件 (Pages/CallJavaScript2.razor
):调用 GetWelcomeMessage
并在组件的 UI 中显示返回的欢迎消息。
CallDotNet2
组件 (Pages/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()
return "Olá do Blazor!";
在上述示例中,应用的命名空间是 BlazorSample
,而 C# 互操作类的完整命名空间是 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();
使 Program.cs
文件顶部的 System.Runtime.InteropServices.JavaScript 命名空间可用:
using System.Runtime.InteropServices.JavaScript;
在调用 WebAssemblyHost.RunAsync 之前在 Program.cs
中加载模块:
if (OperatingSystem.IsBrowser())
await JSHost.ImportAsync("Interop", "../js/interop.js");
Pages/CallJavaScript2.razor
:
@page "/call-javascript-2"
@using BlazorSample.JavaScriptInterop
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 2)
@(message is not null ? message : string.Empty)
@code {
private string? message;
protected override void OnInitialized()
message = Interop.GetWelcomeMessage();
Pages/CallDotNet2.razor
:
@page "/call-dotnet-2"
@using BlazorSample.JavaScriptInterop
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 2)
<span id="result">.NET method not executed</span>
@code {
protected override void OnAfterRender(bool firstRender)
if (firstRender)
Interop.SetWelcomeMessage();
在本节的示例中,JS 互操作用于在 OnAfterRender
中呈现组件后改变 DOM 元素(纯粹出于演示目的)。 通常,当对象不与 Blazor 交互时,应只使用 JS 改变 DOM。 本节中显示的方法类似于在 Razor 组件中使用第三方 JS 库的情况,其中组件通过 JS 互操作与 JS 库交互,第三方 JS 库与 DOM 的一部分进行交互,并且 Blazor 不直接参与对 DOM 的该部分的 DOM 更新。 有关详细信息,请参阅 ASP.NET Core JavaScript 互操作性(JS 互操作)。
API 文档