ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

注意

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关当前版本,请参阅本文.NET 9 版本。

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 JSjQuery

在一些文档示例中,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 函数是一个糟糕的设计选择

建议避免使用内联事件处理程序,而建议使用 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>(具有cleanupDivid)。 从 DOM 中删除组件时,会同时从 DOM 中删除 <div> 元素以及该组件的 DOM 标记的 rest。
  • DOMCleanup.razor.js文件加载DOMCleanupJS类,并调用其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

为了避免记录 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 中删除元素时运行函数。

有关详细信息,请参阅以下文章:

缓存的 JavaScript 文件

Development 环境中进行开发时,JavaScript (JS) 文件和其他静态资产通常不会缓存在客户端上。 在开发过程中,静态资产请求包含值为 no-cachemax-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 上的浏览器上下文中执行。