ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)
注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
Blazor 应用可从 .NET 方法调用 JavaScript (JS) 函数,也可从 JS 函数调用 .NET 方法。 这被称为 JavaScript 互操作性(JS 互操作)。
以下文章中提供了进一步的 JS 互操作指南:
注意
JavaScript [JSImport]
/[JSExport]
互操作 API 适用于 .NET 7 或更高版本中的 ASP.NET Core 中的客户端组件。
有关详细信息,请参阅 JavaScript JSImport/JSExport 与 ASP.NET Core Blazor 互操作。
使用不受信任的数据的交互式服务器组件的压缩
使用默认启用的压缩时,避免创建安全(经过身份验证/授权)的交互式服务器端组件来呈现来自不受信任的源的数据。 不受信任的源包括路由参数、查询字符串、来自 JS 互操作的数据,以及第三方用户可以控制的任何其他数据源(数据库、外部服务)。 有关详细信息,请参阅 ASP.NET CoreBlazorSignalR 指南和 ASP.NET CoreBlazor 交互式服务器端呈现的威胁缓解指南。
JavaScript 互操作抽象和功能包
@microsoft/dotnet-js-interop
包 (npmjs.com
) (Microsoft.JSInterop
NuGet 包) 为 .NET 和 JavaScript (JS) 代码之间的互操作提供抽象和功能。 dotnet/aspnetcore
GitHub 存储库(/src/JSInterop
文件夹)中提供了引用源。 有关详细信息,请参阅 GitHub 存储库的README.md
文件。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
用于在 TypeScript 中编写JS互操作脚本的其他资源:
与 DOM 交互
仅在对象不与Blazor对象交互时,才使用 JavaScript (JS) 改变 DOM。 Blazor 维护 DOM 的表示形式,并直接与 DOM 对象交互。 如果 Blazor 呈现的元素直接使用 JS 或通过 JS 互操作在外部修改,DOM 可能不再匹配 Blazor 的内部表示形式,这可能会导致未定义行为。 未定义行为可能只会干扰元素及其功能的呈现,但也可能给应用或服务器带来安全风险。
本指南不仅适用于你自己的 JS 互操作代码,还适用于应用使用的任何 JS 库,包括第三方框架提供的任何内容,例如 Bootstrap JS 和 jQuery。
在一些文档示例中,JS 互操作用于改变元素,纯粹用于示例中的演示目的。 在这些情况下,文本中会出现警告。
有关详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数。
具有函数类型字段的 JavaScript 类
BlazorJS 互操作不支持具有函数类型字段的 JavaScript 类。 在类中使用 Javascript 函数。
不支持:GreetingHelpers.sayHello
在以下类中,作为函数类型的字段未被 Blazor 的 JS 互操作发现,因此无法从 C# 代码执行:
export class GreetingHelpers {
sayHello = function() {
...
}
}
支持:GreetingHelpers.sayHello
在以下类中,作为函数是受支持的:
export class GreetingHelpers {
sayHello() {
...
}
}
还支持箭头函数:
export class GreetingHelpers {
sayHello = () => {
...
}
}
避免内联事件处理程序
可以直接从内联事件处理程序调用 JavaScript 函数。 在以下示例中,alertUser
是当用户选择按钮时调用的 JavaScript 函数:
<button onclick="alertUser">Click Me!</button>
但是,使用内联事件处理程序来调用 JavaScript 函数是一个糟糕的设计选择:
- 混合 HTML 标记和 JavaScript 代码通常会导致代码无法维护。
- 内容安全策略 (CSP) (MDN 文档) 可能会阻止内联事件处理程序执行。
建议避免使用内联事件处理程序,而建议使用 addEventListener
在 JavaScript 中分配处理程序的方法,如以下示例所示:
AlertUser.razor.js
:
export function alertUser() {
alert('The button was selected!');
}
export function addHandlers() {
const btn = document.getElementById("btn");
btn.addEventListener("click", alertUser);
}
AlertUser.razor
:
@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Alert User</h1>
<p>
<button id="btn">Click Me!</button>
</p>
@code {
private IJSObjectReference? module;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./Components/Pages/AlertUser.razor.js");
await module.InvokeVoidAsync("addHandlers");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
有关更多信息,请参见以下资源:
异步 JavaScript 调用
JS 互操作调用是异步的,无论调用的代码是同步还是异步。 调用是异步的,以确保组件在服务器端和客户端呈现模式之间都兼容。 采用服务器端呈现时,JS互操作调用必须异步,因为它们通过网络连接发送。 对于完全采用客户端呈现的应用,支持同步的JS互操作调用。
有关详细信息,请参阅以下文章:
对象序列化
Blazor 使用 System.Text.Json 进行序列化,要求和默认行为如下:
- 类型必须具有默认的构造函数,
get
/set
访问器必须是公开的,并且字段永远不会序列化。 - 不可自定义全局默认序列化,避免破坏现有组件库、对性能和安全性造成影响以及降低可靠性。
- 序列化 .NET 成员名称会导致生成小写的 JSON 键名称。
- 将 JSON 反序列化为 JsonElement C# 实例,这允许混合大小写。 尽管 JSON 键名称和 C# 属性名称之间存在大小写差异,但用于赋值给 C# 模型属性的内部强制转换都将按预期工作。
- 复杂的框架类型(例如 KeyValuePair,可能会在发布时由 IL 修整程序剪裁掉,而不会出现在 JS 互操作中)。 建议为 IL 剪裁器剪裁的类型创建自定义类型。
- Blazor 始终依赖于反射实现 JSON 序列化,包括使用 C# 源生成时。 在应用的项目文件中将
JsonSerializerIsReflectionEnabledByDefault
设置为false
会导致尝试序列化时出错。
JsonConverter API 可用于自定义序列化。 可以使用 [JsonConverter]
特性对属性进行批注来替代现有数据类型的默认序列化。
有关详细信息,请参阅 .NET 文档中的以下资源:
Blazor 支持优化的字节数组 JS 互操作,这可以避免将字节数组编码/解码为 Base64。 应用可以应用自定义序列化并传递生成的字节。 有关详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数。
在快速序列化大量 .NET 对象时或者当必须对大型 .NET 对象或许多 .NET 对象进行序列化时,Blazor 支持拆收的 JS 互操作。 有关详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数。
组件处置期间的 DOM 清理任务
请勿在组件处置期间执行 DOM 清理任务的 JS 互操作代码。 相反,在客户端上使用采用 JavaScript (JS) 的MutationObserver
模式,原因如下:
- 在
Dispose{Async}
中执行清理代码时,组件可能已从 DOM 中删除。 - 在服务器端呈现期间,Blazor呈现器可能已在
Dispose{Async}
中执行清理代码时由框架处置。
使用 MutationObserver
模式可以在从 DOM 中删除元素时运行函数。
在以下示例中,DOMCleanup
组件:
- 包含
<div>
(具有cleanupDiv
的id
)。 从 DOM 中删除组件时,会同时从 DOM 中删除<div>
元素以及该组件的 DOM 标记的 rest。 - 从
DOMCleanup.razor.js
文件加载DOMCleanup
JS类,并调用其createObserver
函数以设置MutationObserver
回调。 这些任务采用OnAfterRenderAsync
生命周期方法完成。
DOMCleanup.razor
:
@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>DOM Cleanup Example</h1>
<div id="cleanupDiv"></div>
@code {
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Pages/DOMCleanup.razor.js");
await module.InvokeVoidAsync("DOMCleanup.createObserver");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
在以下示例中,每次发生 DOM 更改时都会执行MutationObserver
回调。 当if
语句确认目标元素 (cleanupDiv
) 已删除 (if (targetRemoved) { ... }
) 时,执行清理代码。 必须断开连接并删除MutationObserver
,从而避免清理代码执行后内存泄漏。
DOMCleanup.razor.js
与前面的DOMCleanup
组件并排放置:
export class DOMCleanup {
static observer;
static createObserver() {
const target = document.querySelector('#cleanupDiv');
this.observer = new MutationObserver(function (mutations) {
const targetRemoved = mutations.some(function (mutation) {
const nodes = Array.from(mutation.removedNodes);
return nodes.indexOf(target) !== -1;
});
if (targetRemoved) {
// Cleanup resources here
// ...
// Disconnect and delete MutationObserver
this.observer && this.observer.disconnect();
delete this.observer;
}
});
this.observer.observe(target.parentNode, { childList: true });
}
}
window.DOMCleanup = DOMCleanup;
无线路的 JavaScript 互操作调用
本部分仅适用于服务器端应用。
在 SignalR 线路断开连接后,无法发出 JavaScript (JS) 互操作调用。 如果在组件处置期间或不存在线路的任何其他时间没有线路,以下方法调用将失败,并将线路断开连接的消息记录为 JSDisconnectedException:
- JS 互操作方法调用
Dispose
/DisposeAsync
调用任何 IJSObjectReference。
为了避免记录 JSDisconnectedException 或记录自定义信息,请在 try-catch
语句中捕获异常。
对于以下组件处置示例:
- 组件会实现 IAsyncDisposable。
objInstance
是一种 IJSObjectReference。- JSDisconnectedException 被捕获但未记录。
- (可选)可以在语句
catch
中以你喜欢的任何日志级别记录自定义信息。 以下示例不记录自定义信息,因为它假定开发人员不关心在组件处置期间线路断开连接的时间或位置。
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (objInstance is not null)
{
await objInstance.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
如果必须在线路丢失后清理自己的 JS 对象或在客户端上执行其他 JS 代码,请在客户端上使用 JS 中的 MutationObserver
模式。 使用 MutationObserver
模式可以在从 DOM 中删除元素时运行函数。
有关详细信息,请参阅以下文章:
- 处理 ASP.NET Core Blazor 应用中的错误:“JavaScript 互操作”部分讨论了 JS 互操作方案中的错误处理。
- ASP.NET Core Razor 组件生命周期:“使用
IDisposable
和IAsyncDisposable
处置组件”部分介绍了如何在 Razor 组件中实现处置模式。
缓存的 JavaScript 文件
在 Development
环境中进行开发时,JavaScript (JS) 文件和其他静态资产通常不会缓存在客户端上。 在开发过程中,静态资产请求包含值为 no-cache
或 max-age
(值为零 (0
))的 Cache-Control
标头。
在 Production
环境中的生产阶段,JS 文件通常由客户端缓存。
若要在浏览器中禁用客户端缓存,开发人员通常采用以下方法之一:
- 当浏览器的开发人员工具控制台打开时禁用缓存。 可以在每个浏览器维护程序的开发人员工具文档中找到相关指南:
- 对 Blazor 应用的任意网页执行手动浏览器刷新,以从服务器重载 JS 文件。 ASP.NET Core 的 HTTP 缓存中间件始终遵循客户端发送的有效无缓存
Cache-Control
标头。
有关详细信息,请参阅:
对 JavaScript 互操作调用的大小限制
本部分仅适用于服务器端应用中的交互式组件。 对于客户端组件,框架对 JavaScript (JS) 互操作输入和输出的大小不施加限制。
对于服务器端应用中的交互式组件,将数据从客户端传递到服务器的JS互操作调用受中心方法允许的最大传入SignalR邮件大小限制,该限制由HubOptions.MaximumReceiveMessageSize强制执行(默认值:32 KB)。 JS 到 .NET 的 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。 有关处理邮件大小限制的大小限制、错误消息和指南的详细信息,请参阅ASP.NET Core BlazorSignalR指南。
确定应用在何处运行
如果让应用知道代码在何处运行与 JS 互操作调用相关,则使用 OperatingSystem.IsBrowser 确定组件是否在 WebAssembly 上的浏览器上下文中执行。