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

本文介绍有关如何在 Blazor 应用中与 JavaScript 交互的一般概念。

Blazor 应用可从 .NET 方法调用 JavaScript (JS) 函数,也可从 JS 函数调用 .NET 方法。 这被称为 JavaScript 互操作性(JS 互操作) 。

以下文章中提供了进一步的 JS 互操作指南:

与 DOM 文档对象模型 (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 调用

默认情况下,JS 互操作调用是异步的,无论调用的代码是同步还是异步。 默认情况下,调用是异步的,以确保组件在 Blazor 托管模型 Blazor Server 和 Blazor WebAssembly 之间兼容。 在 Blazor Server 上,JS 互操作调用必须是异步的,因为它们是通过网络连接发送的。 对于完全采用 Blazor WebAssembly 托管模型的应用,支持同步的 JS 互操作调用。

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

对象序列化

Blazor 使用 System.Text.Json 进行序列化,要求和默认行为如下:

  • 类型必须具有默认的构造函数,get/set 访问器必须是公开的,并且字段永远不会序列化。
  • 不可自定义全局默认序列化,避免破坏现有组件库、对性能和安全性造成影响以及降低可靠性。
  • 序列化 .NET 成员名称会导致小写的 JSON 键名称。
  • JSON 会反序列化为 JsonElement C# 实例,这允许混合大小写。 用于赋值给 C# 模型属性的内部强制转换将按预期工作,无论 JSON 键名称和 C# 属性名称之间是否存在任何大小写差异。

JsonConverter API 可用于自定义序列化。 可以使用 [JsonConverter] 特性对属性进行批注来替代现有数据类型的默认序列化。

有关详细信息,请参阅 .NET 文档中的以下资源:

已计划为 ASP.NET Core 7.0 提供针对 System.DateOnlySystem.TimeOnly 的 JSON 序列化程序支持,该版本计划在 2022 年底之前发布。 有关详细信息,请参阅JsonSerializer 中支持 DateOnlyTimeOnly (dotnet/runtime #53539)

Blazor 支持优化的字节数组 JS 互操作,这可以避免将字节数组编码/解码为 Base64。 应用可以应用自定义序列化并传递生成的字节。 有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

在快速序列化大量 .NET 对象时或者当必须对大型 .NET 对象或许多 .NET 对象进行序列化时,Blazor 支持拆收的 JS 互操作。 有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

JavaScript 初始值设定项

JavaScript (JS) 初始值设定项在 Blazor 应用加载之前和之后执行逻辑。 JS 初始值设定项在以下场景中很有用:

  • 自定义 Blazor 应用的加载方式。
  • 在 Blazor 启动之前初始化库。
  • 配置 Blazor 设置。

JS 初始值设定项是在生成过程中检测到的,并自动导入到 Blazor 应用中。 使用 JS 初始值设定项通常无需在使用 Razor 类库 (RCL)从应用中手动触发脚本函数

若要定义 JS 初始值设定项,请向项目添加名为 {NAME}.lib.module.js 的 JS 模块,其中 {NAME} 占位符是程序集名称、库名称或包标识符。 将文件放在项目的 Web 根目录中,通常为 wwwroot 文件夹。

此模块导出以下传统函数之一或同时导出这两个传统函数:

  • beforeStart(options, extensions):在 Blazor 启动之前调用。 例如,beforeStart 用于自定义加载过程、日志记录级别以及其他特定于托管模型的选项。
    • 在 Blazor WebAssembly 中,beforeStart 接收在发布过程中添加的 Blazor WebAssembly 选项(本部分示例中的 options)和任何扩展(本部分示例中的 extensions)。 例如,选项可以指定使用自定义启动资源加载程序
    • 在 Blazor Server 中,beforeStart 接收 SignalR 电路启动选项(本部分示例中的 options)。
    • BlazorWebViews 中,未传递任何选项。
  • afterStarted:在 Blazor 准备好从 JS 接收调用后调用。 例如,afterStarted 用于通过进行 JS 互操作调用并注册自定义元素来初始化库。 Blazor 实例作为参数传递给 afterStarted(本部分示例中的 blazor)。

下面的示例展示了 beforeStartafterStarted 的 JS 初始值设定项。 对于以下示例的文件名:

  • 如果 JS 初始值设定项在项目中是作为静态资产使用的,请在文件名中使用应用的程序集名称。 例如,对于程序集名称为 BlazorSample 的项目,将文件命名为 BlazorSample.lib.module.js。 将文件放在应用的 wwwroot 文件夹中。
  • 如果 JS 初始值设定项是从 RCL 使用的,请使用项目的库名称或包标识符。 例如,对于包标识符为 RazorClassLibrary1 的 RCL,将文件命名为 RazorClassLibrary1.lib.module.js。 将文件放在库的 wwwroot 文件夹中。
export function beforeStart(options, extensions) {
    console.log("beforeStart");
}

export function afterStarted(blazor) {
    console.log("afterStarted");
}

注意

MVC 和 Razor Pages 应用不会自动加载 JS 初始值设定项。 但是,开发人员代码可以包含一个脚本,用于提取应用的清单并触发 JS 初始值设定项的加载。

有关 JS 初始值设定项的示例,请参阅以下资源:

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

JavaScript 的位置

使用以下任一方法加载 JavaScript (JS) 代码:

警告

请勿将 <script> 标记置于 Razor 组件文件 (.razor) 中,因为 <script> 标记无法由 Blazor 动态更新。

注意

文档示例通常将脚本置于 <script> 标记中,或从外部文件加载全局脚本。 这些方法使用全局函数污染客户端。 对于生产应用,建议将 JavaScript 置于单独的 JavaScript 模块中,以便在需要时可进行导入。 有关详细信息,请参阅 JavaScript 模块中的 JavaScript 隔离部分。

<head> 标记中加载脚本

通常不建议使用本部分中的这个方法。

将 JavaScript (JS) 标记 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 的 <head> 元素标记中:

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

<head> 加载 JS 并不是最好的方法,原因如下:

  • 如果脚本依赖于 Blazor,JS 互操作可能会失败。 建议使用一种其他方法加载脚本,而不是通过 <head> 标记加载脚本。
  • 由于分析脚本中的 JS 需要时间,页面的交互速度可能会变慢。

<body> 标记中加载脚本

将 JavaScript (JS) 标记 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 的闭合 </body> 元素标记中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

从与组件并置的外部 JavaScript 文件 (.js) 加载脚本

为页面、视图和 Razor 组件并置 JavaScript (JS) 文件是组织应用中脚本的简便方法。

使用以下文件名扩展约定并置 JS 文件:

  • Razor Pages 应用的页面和 MVC 应用的视图:.cshtml.js。 示例:
    • 对于位于 Pages/Index.cshtml 的 Razor Pages 应用的 Index 页面,使用 Pages/Index.cshtml.js
    • 对于位于 Views/Home/Index.cshtml 的 MVC 应用的 Index 视图,使用 Views/Home/Index.cshtml.js
  • Blazor 应用的 Razor 组件:.razor.js。 示例:对于位于 Pages/Index.razorIndex 组件,使用 Pages/Index.razor.js

使用项目中文件的路径可以公开寻址并置的 JS 文件:

  • 来自应用中并置的脚本文件的页面、视图和组件:

    {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • 占位符 {PATH} 是页面、视图或组件的路径。
    • 占位符 {PAGE, VIEW, OR COMPONENT} 是页面、视图或组件。
    • 占位符 {EXTENSION} 与页面、视图或组件的扩展名匹配,为 razorcshtml

    Razor Pages 示例:

    Index 页面的 JS 文件放置在 Index 页面 (Pages/Index.cshtml) 旁边的 Pages 文件夹 (Pages/Index.cshtml.js) 中。 在 Index 页面中,脚本在 Pages 文件夹中的路径引用:

    @section Scripts {
      <script src="~/Pages/Index.cshtml.js"></script>
    }
    

    发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本将移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js,其中 {TARGET FRAMEWORK MONIKER} 占位符是目标框架名字对象 (TFM)。 无需更改 Index 页面中脚本的相对 URL。

    Blazor 示例:

    Index 组件的 JS 文件放置在 Index 组件 (Pages/Index.razor) 旁边的 Pages 文件夹 (Pages/Index.razor.js) 中。 在 Index 组件中,脚本在 Pages 文件夹中的路径引用。 以下示例基于从 ASP.NET Core 中的 .NET 方法调用 JavaScript 函数Blazor一文中显示的示例。

    Pages/Index.razor.js:

    export function showPrompt(message) {
      return prompt(message, 'Type anything here');
    }
    

    Index 组件 (Pages/Index.razor) 的 OnAfterRenderAsync 方法中:

    module = await JS.InvokeAsync<IJSObjectReference>(
        "import", "./Pages/Index.razor.js");
    

    发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本被移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js,其中 {TARGET FRAMEWORK MONIKER} 占位符是目标框架名字对象 (TFM)。 无需更改 Index 组件中脚本的相对 URL。

  • 对于 Razor 类库 (RCL) 提供的脚本:

    _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • 占位符 {PACKAGE ID} 是 RCL 的包标识符(或由应用引用的类库的库名称)。
    • 占位符 {PATH} 是页面、视图或组件的路径。 如果 Razor 组件位于 RCL 的根目录下,则不包括路径段。
    • 占位符 {PAGE, VIEW, OR COMPONENT} 是页面、视图或组件。
    • 占位符 {EXTENSION} 与页面、视图或组件的扩展名匹配,为 razorcshtml

    在以下 Blazor 应用示例中:

    • RCL 的包标识符是 AppJS
    • 将为 Index 组件 (Index.razor) 加载模块的脚本。
    • Index 组件位于 RCL 的 Pages 文件夹中。
    var module = await JS.InvokeAsync<IJSObjectReference>("import", 
        "./_content/AppJS/Pages/Index.razor.js");
    

有关 RCL 的详细信息,请参阅使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件

从外部 JavaScript 文件 (.js) 加载脚本

将带有脚本源 (src) 路径的 JavaScript (JS) 标记 (<script>...</script>) 置于闭合 </body> 标记中 Blazor 脚本引用的后面。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server{SCRIPT PATH AND FILE NAME (.js)} 占位符是 wwwroot 下的路径和脚本文件名。

在以下前述 <script> 标记的示例中,scripts.js 文件位于应用的 wwwroot/js 文件夹中:

<script src="js/scripts.js"></script>

当外部 JS 文件由 Razor 类库提供时,使用其稳定的静态 Web 资产路径 ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)} 指定 JS 文件:

  • 若要创建 JS 文件的正确静态资产路径,需要当前目录 (./) 的路径段。
  • {PACKAGE ID} 占位符是库的包 ID。 如果项目文件中没有指定 <PackageId>,则包 ID 默认为项目的程序集名称。
  • {SCRIPT PATH AND FILENAME (.js)} 占位符是 wwwroot 下的路径和文件名。
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

在以下前述 <script> 标记的示例中:

  • Razor 类库的程序集名称为 ComponentLibrary,并且库的项目文件中没有指定 <PackageId>
  • scripts.js 文件位于类库的 wwwroot 文件夹中。
<script src="./_content/ComponentLibrary/scripts.js"></script>

有关详细信息,请参阅使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件

在 Blazor 启动后注入脚本

在应用初始化时,从 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 中注入的脚本加载 JS:

  • autostart="false" 添加到加载 Blazor 脚本的 <script> 标记中。
  • 在通过调用 Blazor.start().then(...) 启动 Blazor 之后,向 <head> 元素标记中注入一个引用自定义 JS 文件的脚本。 在加载 Blazor 脚本之后,将脚本 (<script>...</script>) 置于结束的 </body> 标记中。

以下示例在 wwwroot/scripts.js 启动后注入文件 Blazor:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动

JavaScript 模块中的 JavaScript 隔离

Blazor 在标准 Blazor(JS)中启用 JavaScript (JS) 隔离。

JS 隔离具有以下优势:

  • 导入的 JS 不再污染全局命名空间。
  • 库和组件的使用者不需要导入相关的 JS。

有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

缓存的 JavaScript 文件

Development 环境中进行开发时,JavaScript (JS) 文件和其他静态资产通常不会缓存在客户端上。 在开发过程中,静态资产请求包含值为 no-cachemax-age(值为零 (0))的 Cache-Control 标头

Production 环境中的生产阶段,JS 文件通常由客户端缓存。

若要在浏览器中禁用客户端缓存,开发人员通常采用以下方法之一:

有关详细信息,请参阅:

Blazor 应用可从 .NET 方法调用 JavaScript (JS) 函数,也可从 JS 函数调用 .NET 方法。 这被称为 JavaScript 互操作性(JS 互操作) 。

本概述文章涵盖了常规概念。 以下文章中提供了进一步的 JS 互操作指南:

与 DOM 文档对象模型 (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 调用

默认情况下,JS 互操作调用是异步的,无论调用的代码是同步还是异步。 默认情况下,调用是异步的,以确保组件在 Blazor 托管模型 Blazor Server 和 Blazor WebAssembly 之间兼容。 在 Blazor Server 上,JS 互操作调用必须是异步的,因为它们是通过网络连接发送的。 对于完全采用 Blazor WebAssembly 托管模型的应用,支持同步的 JS 互操作调用。

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

对象序列化

Blazor 使用 System.Text.Json 进行序列化,要求和默认行为如下:

  • 类型必须具有默认的构造函数,get/set 访问器必须是公开的,并且字段永远不会序列化。
  • 不可自定义全局默认序列化,避免破坏现有组件库、对性能和安全性造成影响以及降低可靠性。
  • 序列化 .NET 成员名称会导致小写的 JSON 键名称。
  • JSON 会反序列化为 JsonElement C# 实例,这允许混合大小写。 用于赋值给 C# 模型属性的内部强制转换将按预期工作,无论 JSON 键名称和 C# 属性名称之间是否存在任何大小写差异。

JsonConverter API 可用于自定义序列化。 可以使用 [JsonConverter] 特性对属性进行批注来替代现有数据类型的默认序列化。

有关详细信息,请参阅 .NET 文档中的以下资源:

已计划为 ASP.NET Core 7.0 提供针对 System.DateOnlySystem.TimeOnly 的 JSON 序列化程序支持,该版本计划在 2022 年底之前发布。 有关详细信息,请参阅JsonSerializer 中支持 DateOnlyTimeOnly (dotnet/runtime #53539)

在快速序列化大量 .NET 对象时或者当必须对大型 .NET 对象或许多 .NET 对象进行序列化时,Blazor 支持拆收的 JS 互操作。 有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

JavaScript 的位置

使用以下任一方法加载 JavaScript (JS) 代码:

警告

请勿将 <script> 标记置于 Razor 组件文件 (.razor) 中,因为 <script> 标记无法由 Blazor 动态更新。

注意

文档示例通常将脚本置于 <script> 标记中,或从外部文件加载全局脚本。 这些方法使用全局函数污染客户端。 对于生产应用,建议将 JavaScript 置于单独的 JavaScript 模块中,以便在需要时可进行导入。 有关详细信息,请参阅 JavaScript 模块中的 JavaScript 隔离部分。

<head> 标记中加载脚本

通常不建议使用本部分中的这个方法。

将脚本 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的 <head> 元素标记中:

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

<head> 加载 JS 并不是最好的方法,原因如下:

  • 如果脚本依赖于 Blazor,JS 互操作可能会失败。 建议使用一种其他方法加载脚本,而不是通过 <head> 标记加载脚本。
  • 由于分析脚本中的 JS 需要时间,页面的交互速度可能会变慢。

<body> 标记中加载脚本

将脚本 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 元素标记中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

从外部 JS 文件 (.js) 加载脚本

将带有脚本 src 路径的脚本 (<script>...</script>) 置于 Blazor 脚本引用后的结束 </body> 标记中。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server{SCRIPT PATH AND FILE NAME (.js)} 占位符是 wwwroot 下的路径和脚本文件名。

在以下前述 <script> 标记的示例中,scripts.js 文件位于应用的 wwwroot/js 文件夹中:

<script src="js/scripts.js"></script>

当外部 JS 文件由 Razor 类库提供时,使用其稳定的静态 Web 资产路径 ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)} 指定 JS 文件:

  • 若要创建 JS 文件的正确静态资产路径,需要当前目录 (./) 的路径段。
  • {PACKAGE ID} 占位符是库的包 ID。 如果项目文件中没有指定 <PackageId>,则包 ID 默认为项目的程序集名称。
  • {SCRIPT PATH AND FILENAME (.js)} 占位符是 wwwroot 下的路径和文件名。
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

在以下前述 <script> 标记的示例中:

  • Razor 类库的程序集名称为 ComponentLibrary,并且库的项目文件中没有指定 <PackageId>
  • scripts.js 文件位于类库的 wwwroot 文件夹中。
<script src="./_content/ComponentLibrary/scripts.js"></script>

有关详细信息,请参阅使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件

在 Blazor 启动后注入脚本

在应用初始化时,从 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中注入的脚本加载 JS:

  • autostart="false" 添加到加载 Blazor 脚本的 <script> 标记中。
  • 在通过调用 Blazor.start().then(...) 启动 Blazor 之后,向 <head> 元素标记中注入一个引用自定义 JS 文件的脚本。 在加载 Blazor 脚本之后,将脚本 (<script>...</script>) 置于结束的 </body> 标记中。

以下示例在 wwwroot/scripts.js 启动后注入文件 Blazor:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动

JavaScript 模块中的 JavaScript 隔离

Blazor 在标准 Blazor(JS)中启用 JavaScript (JS) 隔离。

JS 隔离具有以下优势:

  • 导入的 JS 不再污染全局命名空间。
  • 库和组件的使用者不需要导入相关的 JS。

有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

缓存的 JavaScript 文件

Development 环境中进行开发时,JavaScript (JS) 文件和其他静态资产通常不会缓存在客户端上。 在开发过程中,静态资产请求包含值为 no-cachemax-age(值为零 (0))的 Cache-Control 标头

Production 环境中的生产阶段,JS 文件通常由客户端缓存。

若要在浏览器中禁用客户端缓存,开发人员通常采用以下方法之一:

有关详细信息,请参阅:

Blazor 应用可从 .NET 方法调用 JavaScript (JS) 函数,也可从 JS 函数调用 .NET 方法。 这被称为 JavaScript 互操作性(JS 互操作) 。

本概述文章涵盖了常规概念。 以下文章中提供了进一步的 JS 互操作指南:

与 DOM 文档对象模型 (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 调用

默认情况下,JS 互操作调用是异步的,无论调用的代码是同步还是异步。 默认情况下,调用是异步的,以确保组件在 Blazor 托管模型 Blazor Server 和 Blazor WebAssembly 之间兼容。 在 Blazor Server 上,JS 互操作调用必须是异步的,因为它们是通过网络连接发送的。 对于完全采用 Blazor WebAssembly 托管模型的应用,支持同步的 JS 互操作调用。

有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

对象序列化

Blazor 使用 System.Text.Json 进行序列化,要求和默认行为如下:

  • 类型必须具有默认的构造函数,get/set 访问器必须是公开的,并且字段永远不会序列化。
  • 不可自定义全局默认序列化,避免破坏现有组件库、对性能和安全性造成影响以及降低可靠性。
  • 序列化 .NET 成员名称会导致小写的 JSON 键名称。
  • JSON 会反序列化为 JsonElement C# 实例,这允许混合大小写。 用于赋值给 C# 模型属性的内部强制转换将按预期工作,无论 JSON 键名称和 C# 属性名称之间是否存在任何大小写差异。

JsonConverter API 可用于自定义序列化。 可以使用 [JsonConverter] 特性对属性进行批注来替代现有数据类型的默认序列化。

有关详细信息,请参阅 .NET 文档中的以下资源:

已计划为 ASP.NET Core 7.0 提供针对 System.DateOnlySystem.TimeOnly 的 JSON 序列化程序支持,该版本计划在 2022 年底之前发布。 有关详细信息,请参阅JsonSerializer 中支持 DateOnlyTimeOnly (dotnet/runtime #53539)

JavaScript 的位置

使用以下任一方法加载 JavaScript (JS) 代码:

警告

请勿将 <script> 标记置于 Razor 组件文件 (.razor) 中,因为 <script> 标记无法由 Blazor 动态更新。

注意

文档示例将脚本置于 <script> 标记中,或从外部文件加载全局脚本。 这些方法使用全局函数污染客户端。 在 ASP.NET Core 5.0 之前的 Blazor 版本中,不支持将 JavaScript 置于单独的 JavaScript 模块(以供在需要时导入)。 如果应用需要使用 JS 模块来进行 JS 隔离,建议使用 ASP.NET Core 5.0 或更高版本来生成应用。 有关详细信息,请使用“版本”下拉列表选择本文的 5.0 或更高版本,并查看“JavaScript 模块中的 JavaScript 隔离”部分。

<head> 标记中加载脚本

通常不建议使用本部分中的这个方法。

将脚本 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的 <head> 元素标记中:

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

<head> 加载 JS 并不是最好的方法,原因如下:

  • 如果脚本依赖于 Blazor,JS 互操作可能会失败。 建议使用一种其他方法加载脚本,而不是通过 <head> 标记加载脚本。
  • 由于分析脚本中的 JS 需要时间,页面的交互速度可能会变慢。

<body> 标记中加载脚本

将脚本 (<script>...</script>) 置于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 元素标记中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

从外部 JS 文件 (.js) 加载脚本

将带有脚本 src 路径的脚本 (<script>...</script>) 置于 Blazor 脚本引用后的结束 </body> 标记中。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server{SCRIPT PATH AND FILE NAME (.js)} 占位符是 wwwroot 下的路径和脚本文件名。

在以下前述 <script> 标记的示例中,scripts.js 文件位于应用的 wwwroot/js 文件夹中:

<script src="js/scripts.js"></script>

当外部 JS 文件由 Razor 类库提供时,使用其稳定的静态 Web 资产路径 ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)} 指定 JS 文件:

  • 若要创建 JS 文件的正确静态资产路径,需要当前目录 (./) 的路径段。
  • {PACKAGE ID} 占位符是库的包 ID。 如果项目文件中没有指定 <PackageId>,则包 ID 默认为项目的程序集名称。
  • {SCRIPT PATH AND FILENAME (.js)} 占位符是 wwwroot 下的路径和文件名。
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

在以下前述 <script> 标记的示例中:

  • Razor 类库的程序集名称为 ComponentLibrary,并且库的项目文件中没有指定 <PackageId>
  • scripts.js 文件位于类库的 wwwroot 文件夹中。
<script src="./_content/ComponentLibrary/scripts.js"></script>

有关详细信息,请参阅使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件

在 Blazor 启动后注入脚本

在应用初始化时,从 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中注入的脚本加载 JS:

  • autostart="false" 添加到加载 Blazor 脚本的 <script> 标记中。
  • 在通过调用 Blazor.start().then(...) 启动 Blazor 之后,向 <head> 元素标记中注入一个引用自定义 JS 文件的脚本。 在加载 Blazor 脚本之后,将脚本 (<script>...</script>) 置于结束的 </body> 标记中。

以下示例在 wwwroot/scripts.js 启动后注入文件 Blazor:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

前面标记中的 {webassembly|server} 占位符是 Blazor WebAssembly 应用 (blazor.webassembly.js) 的 webassembly 或 Blazor Server 应用 (blazor.server.js) 的 server

有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动

缓存的 JavaScript 文件

Development 环境中进行开发时,JavaScript (JS) 文件和其他静态资产通常不会缓存在客户端上。 在开发过程中,静态资产请求包含值为 no-cachemax-age(值为零 (0))的 Cache-Control 标头

Production 环境中的生产阶段,JS 文件通常由客户端缓存。

若要在浏览器中禁用客户端缓存,开发人员通常采用以下方法之一:

有关详细信息,请参阅: