从 ASP.NET Core Blazor 中的 JavaScript 函数调用 .NET 方法

本文介绍如何通过 JavaScript (JS) 调用 .NET 方法。

有关如何从 .NET 调用 JS 函数的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数:

  • DotNet.invokeMethodAsync(推荐):对于 Blazor Server 和 Blazor WebAssembly 应用都是异步的。
  • DotNet.invokeMethod:仅对 Blazor WebAssembly 应用是同步的。

传入包含该方法的程序集的名称、静态 .NET 方法的标识符以及任意自变量。

如下示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。
  • {ARGUMENTS} 占位符是要传递给该方法的以逗号分隔的可选参数,其中每个参数都必须是可执行 JSON 序列化的。
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync 返回表示操作结果的 JS PromiseDotNet.invokeMethod(仅限 Blazor WebAssembly)返回操作的结果。

重要

与同步版本 (invokeMethod) 相比,异步函数 (invokeMethodAsync) 是支持 Blazor Server 场景的首选。

.NET 方法必须是公共的静态方法,并且包含 [JSInvokable] 特性

如下示例中:

  • {<T>} 占位符指示返回类型,只有返回值的方法才需要返回类型。
  • {.NET METHOD ID} 占位符是方法标识符。
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

注意

静态 .NET 方法不支持调用开放式泛型方法,但实例方法支持该操作。 有关详细信息,请参阅调用 .NET 泛型类方法部分。

在下面的 CallDotNetExample1 组件中,ReturnArrayAsync C# 方法返回 int 数组。 [JSInvokable] 特性应用于该方法,这使得该方法可由 JS 调用。

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

<button> 元素的 onclick HTML 特性是 JavaScript 的 onclick 事件处理程序分配,用于处理 click 事件,而不是 Blazor 的 @onclick 指令属性。 returnArrayAsyncJS 函数被指定为处理程序。

以下 returnArrayAsyncJS 函数调用上述 CallDotNetExample1 组件的 ReturnArrayAsync .NET 方法,并将结果记录到浏览器的 Web 开发人员工具控制台。 BlazorSample 是应用的程序集名称。

<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

选择“Trigger .NET static method”按钮时,浏览器的开发人员工具控制台输出会显示数组数据。 各个浏览器的输出格式略有不同。 以下输出显示 Microsoft Edge 使用的格式:

Array(3) [ 1, 2, 3 ]

默认情况下,JS 调用的 .NET 方法标识符是 .NET 方法名称,但你可以使用 [JSInvokable] 特性 构造函数来指定其他标识符。 在以下示例中,DifferentMethodNameReturnArrayAsync 方法的指定方法标识符:

[JSInvokable("DifferentMethodName")]

在对 DotNet.invokeMethodAsyncDotNet.invokeMethod(仅限 Blazor WebAssembly)的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');(仅限 Blazor WebAssembly)

注意

本部分中的 ReturnArrayAsync 方法示例返回 Task 的结果,而不使用显式 C# asyncawait 关键字。 使用 asyncawait 对方法进行编码,是使用 await 关键字返回异步操作值的一种典型方法。

asyncawait 关键字组成的 ReturnArrayAsync 方法:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

有关详细信息,请参阅 C# 指南中的使用 Async 和 Await 的异步编程

创建 JavaScript 对象和数据引用以传递到 .NET

调用 DotNet.createJSObjectReference(jsObject) 以构造 JS 对象引用,以便可以将其传递到 .NET,其中 jsObject 是用于创建 JS 对象引用的 JS 对象。 以下示例将对不可序列化的 window 对象的引用传递给 .NET,后者在 ReceiveWindowObject C# 方法中接收它作为 IJSObjectReference

DotNet.invokeMethodAsync('{APP NAMESPACE}', 'ReceiveWindowObject', 
  DotNet.createJSObjectReference(window));
[JSInvokable]
public void ReceiveWindowObject(IJSObjectReference objRef)
{
    ...
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

调用 DotNet.createJSStreamReference(streamReference) 来构造 JS 流引用,以便可以传递给 .NET,其中 streamReferenceArrayBufferBlob 或任何 类型化数组(例如 Uint8ArrayFloat32Array),用于创建 JS 流引用。

调用实例 .NET 方法

若要从 JavaScript (JS) 调用实例 .NET 方法,请执行以下操作:

  • 通过将实例包装在 DotNetObjectReference 中并对其调用 Create,将 .NET 实例通过引用传递给 JS。
  • 使用传递的 DotNetObjectReference 中的 invokeMethodAsyncinvokeMethod(仅限 Blazor WebAssembly)从 JS 调用 .NET 实例方法。 在从 JS 调用其他 .NET 方法时,也可以将 .NET 实例作为参数传递。
  • 释放 DotNetObjectReference

本文的以下部分演示了调用实例 .NET 方法的各种方法:

DotNetObjectReference 传递到单个 JavaScript 函数

本部分中的示例演示如何将 DotNetObjectReference 传递给单个 JavaScript (JS) 函数。

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMessage .NET 方法:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

对于以下 CallDotNetExample2 组件:

  • 组件有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage
  • 选择“Trigger .NET instance method”按钮后,会使用 DotNetObjectReference 调用 JS 函数 sayHello1
  • sayHello1:
    • 调用 GetHelloMessage 并接收消息结果。
    • 将消息结果返回给进行调用的 TriggerDotNetInstanceMethod 方法。
  • 向用户显示 result 中从 sayHello1 返回的消息。
  • 为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

若要向实例方法传递参数,请执行以下操作:

  1. 向 .NET 方法调用添加参数。 在下面的示例中,一个名称被传递给方法。 根据需要将其他参数添加到列表。

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    

    在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

  2. 向 .NET 方法提供参数列表。

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

DotNetObjectReference 传递到具有多个 JavaScript 函数的类

本部分中的示例演示如何将 DotNetObjectReference 传递给具有多个函数的 JavaScript (JS) 类。

创建一个 DotNetObjectReference 并通过 OnAfterRenderAsync 生命周期方法将其传递到 JS 类,供多个函数使用。 请确保 .NET 代码释放 DotNetObjectReference,如以下示例所示。

在以下 CallDotNetExampleOneHelper 组件中,Trigger JS function 按钮通过设置 JSonclick 属性(而不是 Blazor 的 @onclick 指令特性)来调用 JS 函数

Pages/CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button onclick="GreetingHelpers.sayHello()">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button onclick="GreetingHelpers.welcomeVisitor()">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code {
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            dotNetHelper = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);
        }
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, {name}!";

    public void Dispose()
    {
        dotNetHelper?.Dispose();
    }
}

在上面的示例中:

  • JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • 组件必须显式释放 DotNetObjectReference 以允许垃圾回收并防止内存泄漏。
<script>
  class GreetingHelpers {
    static dotNetHelper;

    static setDotNetHelper(value) {
      GreetingHelpers.dotNetHelper = value;
    }

    static async sayHello() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
      alert(`Message from .NET: "${msg}"`);
    }

    static async welcomeVisitor() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
      alert(`Message from .NET: "${msg}"`);
    }
  }

  window.GreetingHelpers = GreetingHelpers;
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上面的示例中:

  • GreetingHelpers 类添加到 window 对象以全局定义该类,从而允许 Blazor 查找用于 JS 互操作的类。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

调用 .NET 泛型类方法

JavaScript (JS) 函数可以调用 .NET 泛型类方法,其中 JS 函数调用泛型类的 .NET 方法。

在以下泛型类型类 (GenericType<TValue>) 中:

  • 类具有单个类型参数 (TValue) 和单个泛型 Value 属性。
  • 类具有两个标记有 [JSInvokable] 特性的非泛型方法,每个方法都有一个名为 newValue 的泛型类型参数:
    • UpdatenewValue 同步更新 Value 的值。
    • 使用 Task.Yield 创建在等待时异步产生当前上下文的可等待任务时,UpdateAsyncnewValue 异步更新 Value 的值。
  • 每个类方法都将 TValue 的类型和 Value 的值写入控制台。 写入控制台操作仅用于演示目的。 生产应用通常避免写入控制台,而是支持应用日志记录。 有关详细信息,请参阅 ASP.NET Core Blazor 日志记录登录 .NET Core 和 ASP.NET Core

注意

开放式泛型类型和方法不为类型占位符指定类型。 相反,封闭式泛型为所有类型占位符提供类型。 本部分中的示例演示了封闭式泛型,但支持使用开放式泛型调用 JS 互操作实例方法静态 .NET 方法调用不支持使用开放式泛型,本文前面已对此进行了介绍

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

GenericType.cs:

using Microsoft.JSInterop;

public class GenericType<TValue>
{
    public TValue? Value { get; set; }

    [JSInvokable]
    public void Update(TValue newValue)
    {
        Value = newValue;

        Console.WriteLine($"Update: GenericType<{typeof(TValue)}>: {Value}");
    }

    [JSInvokable]
    public async void UpdateAsync(TValue newValue)
    {
        await Task.Yield();
        Value = newValue;

        Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>: {Value}");
    }
}

在以下 invokeMethodsAsync 函数中:

  • 泛型类型类的 UpdateUpdateAsync 方法是通过表示字符串和数字的参数调用的。
  • Blazor WebAssembly 应用支持与 invokeMethod 同步调用 .NET 方法。 syncInterop 接收一个布尔值,该值指示 Blazor WebAssembly 应用中是否发生 JS 互操作。 如果 syncInteroptrue,则可安全地调用 invokeMethod。 如果 syncInterop 的值为 false,则仅调用异步函数 invokeMethodAsync,因为 JS 互操作是在 Blazor Server 应用中执行的。
  • 出于演示目的,将 DotNetObjectReference 函数调用(invokeMethodinvokeMethodAsync)、调用的 .NET 方法(UpdateUpdateAsync)以及参数写入控制台。 参数使用一个随机数,以允许将 JS 函数调用与 .NET 方法调用相匹配(也会写入 .NET 端的控制台)。 通常,在客户端或服务器上,生产代码不会写入控制台。 生产应用通常依赖于应用日志记录。 有关详细信息,请参阅 ASP.NET Core Blazor 日志记录登录 .NET Core 和 ASP.NET Core
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

  window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => {
    var n = randomInt();
    console.log(`JS: invokeMethodAsync:Update('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update('string ${n}')`);
      dotNetHelper1.invokeMethod('Update', `string ${n}`);
    }

    n = randomInt();
    console.log(`JS: invokeMethodAsync:Update(${n})`);
    await dotNetHelper2.invokeMethodAsync('Update', n);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);
    await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update(${n})`);
      dotNetHelper2.invokeMethod('Update', n);
    }
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在下面的 GenericsExample 组件中:

  • 选择 Invoke Interop 按钮时会调用 JS 函数 invokeMethodsAsync
  • 将为 GenericType 的实例创建一对 DotNetObjectReference 类型,并将其作为 stringint 传递给 JS 函数。

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JS
@implements IDisposable

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code {
    private GenericType<string> genericType1 = new() { Value = "string 0" };
    private GenericType<int> genericType2 = new() { Value = 0 };
    private DotNetObjectReference<GenericType<string>>? objRef1;
    private DotNetObjectReference<GenericType<int>>? objRef2;

    protected override void OnInitialized()
    {
        objRef1 = DotNetObjectReference.Create(genericType1);
        objRef2 = DotNetObjectReference.Create(genericType2);
    }

    public async Task InvokeInterop()
    {
        var syncInterop =
            RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync", syncInterop, objRef1, objRef2);
    }

    public void Dispose()
    {
        objRef1?.Dispose();
        objRef2?.Dispose();
    }
}

在前面的示例中,JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。

下面演示了在 Blazor WebAssembly 应用中选择 Invoke Interop 按钮时上述示例的典型输出:

JS: invokeMethodAsync:Update('string 37802')
.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync('string 53051')
JS: invokeMethod:Update('string 26784')
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

如果前面的示例是在 Blazor Server 应用中实现的,则将避免与 invokeMethod 的同步调用。 在 Blazor Server 方案中,异步函数 (invokeMethodAsync) 优于同步版本 (invokeMethod)。

Blazor Server 应用的典型输出如下:

JS: invokeMethodAsync:Update('string 34809')
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

上述输出示例演示了异步方法以任意顺序执行和完成,该过程具体取决于多个因素,包括线程计划和方法执行速度。 无法可靠地预测异步方法调用的完成顺序。

类实例示例

以下 sayHello1JS 函数:

  • 对传递的 DotNetObjectReference 调用 GetHelloMessage .NET 方法。
  • 将消息从 GetHelloMessage 返回给 sayHello1 调用方。
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

以下 HelloHelper 类有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage。 创建 HelloHelper 时,Name 属性中的名称用于从 GetHelloMessage 返回消息。

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No Name";
    }

    public string? Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}

以下 JsInteropClasses3 类中的 CallHelloHelperGetHelloMessage 方法使用一个新实例 HelloHelper 调用 JS 函数 sayHello1

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

    public JsInteropClasses3(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

在以下 CallDotNetExample4 组件中选择“Trigger .NET instance method”按钮后,将使用值 name 调用 JsInteropClasses3.CallHelloHelperGetHelloMessage

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

下图显示了在 Name 字段中具有名称 Amy Pond 的呈现组件。 选择该按钮后,UI 中将显示 Hello, Amy Pond!

呈现的“CallDotNetExample4”组件示例

还可以在组件中完全实现上述 JsInteropClasses3 类中所示的模式。

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

CallDotNetExample5 组件显示的输出是在 name 字段中提供名称 Amy Pond 时的 Hello, Amy Pond!

在前面的 CallDotNetExample5 组件中,释放了 .NET 对象引用。 如果某个类或组件没有释放 DotNetObjectReference,则通过对传递的 DotNetObjectReference 调用 dispose 从客户端对其进行释放:

window.{JS FUNCTION NAME} = (dotNetHelper) => {
  dotNetHelper.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}');
  dotNetHelper.dispose();
}

在上面的示例中:

  • {JS FUNCTION NAME} 占位符是 JS 函数的名称。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

组件实例 .NET 方法帮助程序类

帮助程序类可以将 .NET 实例方法作为 Action 进行调用。 帮助程序类在以下情况下很有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。

如下示例中:

  • CallDotNetExample6 组件包含多个 ListItem1 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem1 组件都由一个消息和一个按钮组成。
  • 选择 ListItem1 组件按钮后,ListItem1UpdateMessage 方法会更改列表项文本并隐藏该按钮。

以下 MessageUpdateInvokeHelper 类维护一个可进行 JS 调用的 .NET 方法 UpdateMessageCaller,以调用在实例化类时指定的 ActionBlazorSample 是应用的程序集名称。

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

以下 updateMessageCallerJS 函数调用 UpdateMessageCaller .NET 方法。 BlazorSample 是应用的程序集名称。

<script>
  window.updateMessageCaller = (dotNetHelper) => {
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem1 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。 每个 ListItem1 组件实例都建立了一个 MessageUpdateInvokeHelper 的实例,其中 Action 设置为其 UpdateMessage 方法。

选择 ListItem1 组件的“InteropCall”按钮后,就会使用为 MessageUpdateInvokeHelper 实例创建的 DotNetObjectReference 调用 updateMessageCaller。 这允许框架对该 ListItem1MessageUpdateInvokeHelper 实例调用 UpdateMessageCaller。 传递的 DotNetObjectReference 在 JS (dotNetHelper.dispose()) 中被释放。

Shared/ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

StateHasChanged 被调用,以在 UpdateMessage 中设置 message 时更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用 Action 时是否应更新 UI。

以下 CallDotNetExample6 父组件包括四个列表项,每个列表项都是 ListItem1 组件的一个实例。

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

下图显示了在选择第二个“InteropCall”按钮后呈现的 CallDotNetExample6 父组件:

  • 第二个 ListItem1 组件已显示 UpdateMessage Called! 消息。
  • 第二个 ListItem1 组件的“InteropCall”按钮不可见,因为该按钮的 CSS display 属性被设置为 none

呈现的“CallDotNetExample6”组件示例

从分配给元素属性的 DotNetObjectReference 调用的组件实例 .NET 方法

DotNetObjectReference 分配给 HTML 元素的属性允许在组件实例上调用 .NET 方法:

组件实例 .NET 方法帮助程序类部分中描述的方法类似,此方法在以下场景中非常有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。
  • .NET 方法调用自 JS 事件(例如 onclick),而非调用自 Blazor 事件(例如 @onclick)。

如下示例中:

  • CallDotNetExample7 组件包含多个 ListItem2 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem2 组件由列表项消息 <span> 和第二个 <span> 组成,其中 display CSS 属性设置为 inline-block 以进行显示。
  • 当选择 ListItem2 组件列表项时,ListItem2UpdateMessage 方法会更改第一个 <span> 中的列表项文本,并通过将其 display 属性设置为 none 来隐藏第二个 <span>

以下 assignDotNetHelperJS 函数将 DotNetObjectReference 分配给名为 dotNetHelper 的属性中的元素:

<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }
</script>

以下 interopCallJS 函数对传递的元素使用 DotNetObjectReference 来调用名为 UpdateMessage 的 .NET 方法:

<script>
  window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem2 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。

每个 ListItem2 组件实例使用元素引用(列表项的第一个 <span> 元素)调用 OnAfterRenderAsync 中的 assignDotNetHelperJS 函数,组件实例作为 DotNetObjectReference

选择 ListItem2 组件的消息 <span> 时,将调用将 <span> 元素作为参数 (this) 传递的 interopCall,该参数将调用 UpdateMessage .NET 方法。 在 UpdateMessage 中,当设置 message 并更新第二个 <span>display 属性时,调用 StateHasChanged 来更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用该方法时是否应更新 UI。

在释放组件时释放 DotNetObjectReference

Shared/ListItem2.razor:

@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}

以下 CallDotNetExample7 父组件包括四个列表项,每个列表项都是 ListItem2 组件的一个实例。

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

Blazor WebAssembly 应用中的同步 JS 互操作

本部分仅适用于 应用。

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

如果你确定你的应用只在 Blazor WebAssembly 上运行,则可以选择执行同步 JS 互操作调用。 这比进行异步调用的开销略少,并且可能会导致呈现周期更少,因为在等待结果时没有中间状态。

若要在 Blazor WebAssembly 应用中进行从 JavaScript 到 .NET 的同步调用,请使用 DotNet.invokeMethod 而不是 DotNet.invokeMethodAsync

同步调用在以下情况下起作用:

  • 应用在 Blazor WebAssembly(而不是 Blazor Server)上运行。
  • 调用的函数以同步方式返回值。 该函数不是 async 方法,不会返回 .NET Task 或 JavaScript Promise

JavaScript 的位置

使用 JS 互操作概述文章中所述的任何方法加载 JavaScript (JS) 代码:

有关在 模块中隔离脚本的信息,请参阅 JavaScript 模块中的 JavaScript 隔离部分。

警告

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

JavaScript 模块中的 JavaScript 隔离

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

JS 隔离具有以下优势:

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

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

避免循环引用对象

不能在客户端上针对以下调用就包含循环引用的对象进行序列化:

  • .NET 方法调用。
  • 返回类型具有循环引用时,从 C# 发出的 JavaScript 方法调用。

字节数组支持

Blazor 支持优化的字节数组 JavaScript (JS) 互操作,这可以避免将字节数组编码/解码为 Base64。 以下示例使用 JS 互操作将字节数组传递给 .NET。

提供 sendByteArrayJS 函数。 该函数通过组件中的按钮进行调用,不返回值:

<script>
  window.sendByteArray = () => {
    const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
      0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
      0x20,0x43,0x61,0x70,0x74,0x69,0x61,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

Pages/CallDotNetExample8.razor:

@page "/call-dotnet-example-8"
@using System.Text

<h1>Call .NET Example 8</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

有关从 .NET 调用 JavaScript 时使用字节数组的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

从 JavaScript 流式传输到 .NET

Blazor 支持将数据直接从 JavaScript 流式传输到 .NET。 使用 Microsoft.JSInterop.IJSStreamReference 接口请求流。

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync 返回 Stream 并使用以下参数:

  • maxAllowedSize:JavaScript 中读取操作允许的最大字节数,如果未指定,则默认为 512,000 个字节。
  • cancellationTokenCancellationToken 用于取消读取。

在 JavaScript 中:

function streamToDotNet() {
  return new Uint8Array(10000000);
}

在 C# 代码中:

var dataReference = 
    await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream = 
    await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

在上面的示例中:

  • JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。
  • dataReferenceStream 被写入磁盘 (file.txt) 的当前用户临时文件夹路径 (GetTempPath) 中。

在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数介绍了反向操作,即使用 DotNetStreamReference 从 .NET 流式传输到 JavaScript。

ASP.NET Core Blazor 文件上传介绍了如何在 Blazor 中上传文件。

对 JavaScript 互操作调用的大小限制

本部分仅适用于 Blazor Server应用。 在 Blazor WebAssembly 中,框架对 JavaScript (JS) 互操作输入和输出的大小不施加限制。

在 Blazor Server 中,JS 互操作调用的大小受中心方法允许的最大传入 SignalR 消息大小限制,该限制由 HubOptions.MaximumReceiveMessageSize(默认值:32 KB)强制执行。 JS 到 .NET 的 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。

如果未将 SignalR 日志记录设置为SignalR或跟踪,则消息大小错误仅显示在浏览器的开发人员工具控制台中:

错误:连接断开,出现错误“错误:服务器关闭时返回错误:连接因错误而关闭。”

服务器端日志记录设置为调试跟踪时,服务器端日志记录会针对消息大小错误显示

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

错误:

System.IO.InvalidDataException:超出了最大消息大小 32768B。 消息大小可以在 AddHubOptions 中配置。

通过在 Program.cs 中设置 MaximumReceiveMessageSize 来提高限制。 以下示例将最大接收消息大小设置为 64 KB:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

提高 SignalR 传入消息大小上限的代价是需要使用更多的服务器资源,这将使服务器面临来自恶意用户的更大风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。

在 Blazor Server 应用中开发在 JS 和 Blazor 之间传输大量数据的代码时,请考虑以下指南:

  • 利用本机流式处理互操作支持来传输大于 SignalR 传入消息大小限制的数据:
  • 常规提示:
    • 不要在 JS 和 C# 代码中分配大型对象。
    • 在进程完成或取消时释放已消耗的内存。
    • 为了安全起见,请强制执行以下附加要求:
      • 声明可以传递的最大文件或数据大小。
      • 声明从客户端到服务器的最低上传速率。
    • 在服务器收到数据后,数据可以:
      • 暂时存储在内存缓冲区中,直到收集完所有数据段。
      • 立即使用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。

JavaScript [JSImport]/[JSExport] 互操作

本部分适用于 Blazor WebAssembly 应用。

作为基于 IJSRuntime 接口使用 Blazor 的 JS 互操作机制与 Blazor WebAssembly 应用中的 JavaScript (JS) 交互的替代方法,JS[JSImport]/[JSExport] 互操作 API 可用于面向 .NET 7 或更高版本的应用。

有关详细信息,请参阅 JavaScript JSImport/JSExport 与 ASP.NET Core Blazor WebAssembly 互操作

组件处置期间的文档对象模型 (DOM) 清理任务

请勿在组件处置期间执行 DOM 清理任务的 JS 互操作代码。 相反,在客户端上使用采用 JavaScript 的 MutationObserver 模式,原因如下:

  • Dispose{Async} 中执行清理代码时,组件可能已从 DOM 中删除。
  • 在 Blazor Server 应用中,Blazor 呈现器可能已在 Dispose{Async} 中执行清理代码时由框架处置。

使用 MutationObserver 模式可以在从 DOM 中删除元素时运行函数。

无线路的 JavaScript 互操作调用

本部分仅适用于 Blazor Server应用。

在 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 中删除元素时运行函数。

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

其他资源

有关如何从 .NET 调用 JS 函数的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数:

  • DotNet.invokeMethodAsync(推荐):对于 Blazor Server 和 Blazor WebAssembly 应用都是异步的。
  • DotNet.invokeMethod:仅对 Blazor WebAssembly 应用是同步的。

传入包含该方法的程序集的名称、静态 .NET 方法的标识符以及任意自变量。

如下示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。
  • {ARGUMENTS} 占位符是要传递给该方法的以逗号分隔的可选参数,其中每个参数都必须是可执行 JSON 序列化的。
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync 返回表示操作结果的 JS PromiseDotNet.invokeMethod(仅限 Blazor WebAssembly)返回操作的结果。

重要

与同步版本 (invokeMethod) 相比,异步函数 (invokeMethodAsync) 是支持 Blazor Server 场景的首选。

.NET 方法必须是公共的静态方法,并且包含 [JSInvokable] 特性

如下示例中:

  • {<T>} 占位符指示返回类型,只有返回值的方法才需要返回类型。
  • {.NET METHOD ID} 占位符是方法标识符。
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

注意

静态 .NET 方法不支持调用开放式泛型方法,但实例方法支持该操作。 有关详细信息,请参阅调用 .NET 泛型类方法部分。

在下面的 CallDotNetExample1 组件中,ReturnArrayAsync C# 方法返回 int 数组。 [JSInvokable] 特性应用于该方法,这使得该方法可由 JS 调用。

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

<button> 元素的 onclick HTML 特性是 JavaScript 的 onclick 事件处理程序分配,用于处理 click 事件,而不是 Blazor 的 @onclick 指令属性。 returnArrayAsyncJS 函数被指定为处理程序。

以下 returnArrayAsyncJS 函数调用上述 CallDotNetExample1 组件的 ReturnArrayAsync .NET 方法,并将结果记录到浏览器的 Web 开发人员工具控制台。 BlazorSample 是应用的程序集名称。

<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

选择“Trigger .NET static method”按钮时,浏览器的开发人员工具控制台输出会显示数组数据。 各个浏览器的输出格式略有不同。 以下输出显示 Microsoft Edge 使用的格式:

Array(3) [ 1, 2, 3 ]

默认情况下,JS 调用的 .NET 方法标识符是 .NET 方法名称,但你可以使用 [JSInvokable] 特性 构造函数来指定其他标识符。 在以下示例中,DifferentMethodNameReturnArrayAsync 方法的指定方法标识符:

[JSInvokable("DifferentMethodName")]

在对 DotNet.invokeMethodAsyncDotNet.invokeMethod(仅限 Blazor WebAssembly)的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');(仅限 Blazor WebAssembly)

注意

本部分中的 ReturnArrayAsync 方法示例返回 Task 的结果,而不使用显式 C# asyncawait 关键字。 使用 asyncawait 对方法进行编码,是使用 await 关键字返回异步操作值的一种典型方法。

asyncawait 关键字组成的 ReturnArrayAsync 方法:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

有关详细信息,请参阅 C# 指南中的使用 Async 和 Await 的异步编程

创建 JavaScript 对象和数据引用以传递到 .NET

调用 DotNet.createJSObjectReference(jsObject) 以构造 JS 对象引用,以便可以将其传递到 .NET,其中 jsObject 是用于创建 JS 对象引用的 JS 对象。 以下示例将对不可序列化的 window 对象的引用传递给 .NET,后者在 ReceiveWindowObject C# 方法中接收它作为 IJSObjectReference

DotNet.invokeMethodAsync('{APP NAMESPACE}', 'ReceiveWindowObject', 
  DotNet.createJSObjectReference(window));
[JSInvokable]
public void ReceiveWindowObject(IJSObjectReference objRef)
{
    ...
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

调用 DotNet.createJSStreamReference(streamReference) 来构造 JS 流引用,以便可以传递给 .NET,其中 streamReferenceArrayBufferBlob 或任何 类型化数组(例如 Uint8ArrayFloat32Array),用于创建 JS 流引用。

调用实例 .NET 方法

若要从 JavaScript (JS) 调用实例 .NET 方法,请执行以下操作:

  • 通过将实例包装在 DotNetObjectReference 中并对其调用 Create,将 .NET 实例通过引用传递给 JS。
  • 使用传递的 DotNetObjectReference 中的 invokeMethodAsyncinvokeMethod(仅限 Blazor WebAssembly)从 JS 调用 .NET 实例方法。 在从 JS 调用其他 .NET 方法时,也可以将 .NET 实例作为参数传递。
  • 释放 DotNetObjectReference

本文的以下部分演示了调用实例 .NET 方法的各种方法:

DotNetObjectReference 传递到单个 JavaScript 函数

本部分中的示例演示如何将 DotNetObjectReference 传递给单个 JavaScript (JS) 函数。

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMessage .NET 方法:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

对于以下 CallDotNetExample2 组件:

  • 组件有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage
  • 选择“Trigger .NET instance method”按钮后,会使用 DotNetObjectReference 调用 JS 函数 sayHello1
  • sayHello1:
    • 调用 GetHelloMessage 并接收消息结果。
    • 将消息结果返回给进行调用的 TriggerDotNetInstanceMethod 方法。
  • 向用户显示 result 中从 sayHello1 返回的消息。
  • 为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

若要向实例方法传递参数,请执行以下操作:

  1. 向 .NET 方法调用添加参数。 在下面的示例中,一个名称被传递给方法。 根据需要将其他参数添加到列表。

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    

    在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

  2. 向 .NET 方法提供参数列表。

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

DotNetObjectReference 传递到具有多个 JavaScript 函数的类

本部分中的示例演示如何将 DotNetObjectReference 传递给具有多个函数的 JavaScript (JS) 类。

创建一个 DotNetObjectReference 并通过 OnAfterRenderAsync 生命周期方法将其传递到 JS 类,供多个函数使用。 请确保 .NET 代码释放 DotNetObjectReference,如以下示例所示。

在以下 CallDotNetExampleOneHelper 组件中,Trigger JS function 按钮通过设置 JSonclick 属性(而不是 Blazor 的 @onclick 指令特性)来调用 JS 函数

Pages/CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button onclick="GreetingHelpers.sayHello()">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button onclick="GreetingHelpers.welcomeVisitor()">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code {
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            dotNetHelper = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);
        }
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, {name}!";

    public void Dispose()
    {
        dotNetHelper?.Dispose();
    }
}

在上面的示例中:

  • JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • 组件必须显式释放 DotNetObjectReference 以允许垃圾回收并防止内存泄漏。
<script>
  class GreetingHelpers {
    static dotNetHelper;

    static setDotNetHelper(value) {
      GreetingHelpers.dotNetHelper = value;
    }

    static async sayHello() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
      alert(`Message from .NET: "${msg}"`);
    }

    static async welcomeVisitor() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
      alert(`Message from .NET: "${msg}"`);
    }
  }

  window.GreetingHelpers = GreetingHelpers;
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上面的示例中:

  • GreetingHelpers 类添加到 window 对象以全局定义该类,从而允许 Blazor 查找用于 JS 互操作的类。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

调用 .NET 泛型类方法

JavaScript (JS) 函数可以调用 .NET 泛型类方法,其中 JS 函数调用泛型类的 .NET 方法。

在以下泛型类型类 (GenericType<TValue>) 中:

  • 类具有单个类型参数 (TValue) 和单个泛型 Value 属性。
  • 类具有两个标记有 [JSInvokable] 特性的非泛型方法,每个方法都有一个名为 newValue 的泛型类型参数:
    • UpdatenewValue 同步更新 Value 的值。
    • 使用 Task.Yield 创建在等待时异步产生当前上下文的可等待任务时,UpdateAsyncnewValue 异步更新 Value 的值。
  • 每个类方法都将 TValue 的类型和 Value 的值写入控制台。 写入控制台操作仅用于演示目的。 生产应用通常避免写入控制台,而是支持应用日志记录。 有关详细信息,请参阅 ASP.NET Core Blazor 日志记录登录 .NET Core 和 ASP.NET Core

注意

开放式泛型类型和方法不为类型占位符指定类型。 相反,封闭式泛型为所有类型占位符提供类型。 本部分中的示例演示了封闭式泛型,但支持使用开放式泛型调用 JS 互操作实例方法静态 .NET 方法调用不支持使用开放式泛型,本文前面已对此进行了介绍

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

GenericType.cs:

using Microsoft.JSInterop;

public class GenericType<TValue>
{
    public TValue? Value { get; set; }

    [JSInvokable]
    public void Update(TValue newValue)
    {
        Value = newValue;

        Console.WriteLine($"Update: GenericType<{typeof(TValue)}>: {Value}");
    }

    [JSInvokable]
    public async void UpdateAsync(TValue newValue)
    {
        await Task.Yield();
        Value = newValue;

        Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>: {Value}");
    }
}

在以下 invokeMethodsAsync 函数中:

  • 泛型类型类的 UpdateUpdateAsync 方法是通过表示字符串和数字的参数调用的。
  • Blazor WebAssembly 应用支持与 invokeMethod 同步调用 .NET 方法。 syncInterop 接收一个布尔值,该值指示 Blazor WebAssembly 应用中是否发生 JS 互操作。 如果 syncInteroptrue,则可安全地调用 invokeMethod。 如果 syncInterop 的值为 false,则仅调用异步函数 invokeMethodAsync,因为 JS 互操作是在 Blazor Server 应用中执行的。
  • 出于演示目的,将 DotNetObjectReference 函数调用(invokeMethodinvokeMethodAsync)、调用的 .NET 方法(UpdateUpdateAsync)以及参数写入控制台。 参数使用一个随机数,以允许将 JS 函数调用与 .NET 方法调用相匹配(也会写入 .NET 端的控制台)。 通常,在客户端或服务器上,生产代码不会写入控制台。 生产应用通常依赖于应用日志记录。 有关详细信息,请参阅 ASP.NET Core Blazor 日志记录登录 .NET Core 和 ASP.NET Core
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

  window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => {
    var n = randomInt();
    console.log(`JS: invokeMethodAsync:Update('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update('string ${n}')`);
      dotNetHelper1.invokeMethod('Update', `string ${n}`);
    }

    n = randomInt();
    console.log(`JS: invokeMethodAsync:Update(${n})`);
    await dotNetHelper2.invokeMethodAsync('Update', n);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);
    await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update(${n})`);
      dotNetHelper2.invokeMethod('Update', n);
    }
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在下面的 GenericsExample 组件中:

  • 选择 Invoke Interop 按钮时会调用 JS 函数 invokeMethodsAsync
  • 将为 GenericType 的实例创建一对 DotNetObjectReference 类型,并将其作为 stringint 传递给 JS 函数。

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JS
@implements IDisposable

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code {
    private GenericType<string> genericType1 = new() { Value = "string 0" };
    private GenericType<int> genericType2 = new() { Value = 0 };
    private DotNetObjectReference<GenericType<string>>? objRef1;
    private DotNetObjectReference<GenericType<int>>? objRef2;

    protected override void OnInitialized()
    {
        objRef1 = DotNetObjectReference.Create(genericType1);
        objRef2 = DotNetObjectReference.Create(genericType2);
    }

    public async Task InvokeInterop()
    {
        var syncInterop =
            RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync", syncInterop, objRef1, objRef2);
    }

    public void Dispose()
    {
        objRef1?.Dispose();
        objRef2?.Dispose();
    }
}

在前面的示例中,JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。

下面演示了在 Blazor WebAssembly 应用中选择 Invoke Interop 按钮时上述示例的典型输出:

JS: invokeMethodAsync:Update('string 37802')
.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync('string 53051')
JS: invokeMethod:Update('string 26784')
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

如果前面的示例是在 Blazor Server 应用中实现的,则将避免与 invokeMethod 的同步调用。 在 Blazor Server 方案中,异步函数 (invokeMethodAsync) 优于同步版本 (invokeMethod)。

Blazor Server 应用的典型输出如下:

JS: invokeMethodAsync:Update('string 34809')
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

上述输出示例演示了异步方法以任意顺序执行和完成,该过程具体取决于多个因素,包括线程计划和方法执行速度。 无法可靠地预测异步方法调用的完成顺序。

类实例示例

以下 sayHello1JS 函数:

  • 对传递的 DotNetObjectReference 调用 GetHelloMessage .NET 方法。
  • 将消息从 GetHelloMessage 返回给 sayHello1 调用方。
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

以下 HelloHelper 类有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage。 创建 HelloHelper 时,Name 属性中的名称用于从 GetHelloMessage 返回消息。

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No Name";
    }

    public string? Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}

以下 JsInteropClasses3 类中的 CallHelloHelperGetHelloMessage 方法使用一个新实例 HelloHelper 调用 JS 函数 sayHello1

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

    public JsInteropClasses3(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

在以下 CallDotNetExample4 组件中选择“Trigger .NET instance method”按钮后,将使用值 name 调用 JsInteropClasses3.CallHelloHelperGetHelloMessage

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

下图显示了在 Name 字段中具有名称 Amy Pond 的呈现组件。 选择该按钮后,UI 中将显示 Hello, Amy Pond!

呈现的“CallDotNetExample4”组件示例

还可以在组件中完全实现上述 JsInteropClasses3 类中所示的模式。

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

CallDotNetExample5 组件显示的输出是在 name 字段中提供名称 Amy Pond 时的 Hello, Amy Pond!

在前面的 CallDotNetExample5 组件中,释放了 .NET 对象引用。 如果某个类或组件没有释放 DotNetObjectReference,则通过对传递的 DotNetObjectReference 调用 dispose 从客户端对其进行释放:

window.{JS FUNCTION NAME} = (dotNetHelper) => {
  dotNetHelper.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}');
  dotNetHelper.dispose();
}

在上面的示例中:

  • {JS FUNCTION NAME} 占位符是 JS 函数的名称。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

组件实例 .NET 方法帮助程序类

帮助程序类可以将 .NET 实例方法作为 Action 进行调用。 帮助程序类在以下情况下很有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。

如下示例中:

  • CallDotNetExample6 组件包含多个 ListItem1 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem1 组件都由一个消息和一个按钮组成。
  • 选择 ListItem1 组件按钮后,ListItem1UpdateMessage 方法会更改列表项文本并隐藏该按钮。

以下 MessageUpdateInvokeHelper 类维护一个可进行 JS 调用的 .NET 方法 UpdateMessageCaller,以调用在实例化类时指定的 ActionBlazorSample 是应用的程序集名称。

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

以下 updateMessageCallerJS 函数调用 UpdateMessageCaller .NET 方法。 BlazorSample 是应用的程序集名称。

<script>
  window.updateMessageCaller = (dotNetHelper) => {
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem1 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。 每个 ListItem1 组件实例都建立了一个 MessageUpdateInvokeHelper 的实例,其中 Action 设置为其 UpdateMessage 方法。

选择 ListItem1 组件的“InteropCall”按钮后,就会使用为 MessageUpdateInvokeHelper 实例创建的 DotNetObjectReference 调用 updateMessageCaller。 这允许框架对该 ListItem1MessageUpdateInvokeHelper 实例调用 UpdateMessageCaller。 传递的 DotNetObjectReference 在 JS (dotNetHelper.dispose()) 中被释放。

Shared/ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

StateHasChanged 被调用,以在 UpdateMessage 中设置 message 时更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用 Action 时是否应更新 UI。

以下 CallDotNetExample6 父组件包括四个列表项,每个列表项都是 ListItem1 组件的一个实例。

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

下图显示了在选择第二个“InteropCall”按钮后呈现的 CallDotNetExample6 父组件:

  • 第二个 ListItem1 组件已显示 UpdateMessage Called! 消息。
  • 第二个 ListItem1 组件的“InteropCall”按钮不可见,因为该按钮的 CSS display 属性被设置为 none

呈现的“CallDotNetExample6”组件示例

从分配给元素属性的 DotNetObjectReference 调用的组件实例 .NET 方法

DotNetObjectReference 分配给 HTML 元素的属性允许在组件实例上调用 .NET 方法:

组件实例 .NET 方法帮助程序类部分中描述的方法类似,此方法在以下场景中非常有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。
  • .NET 方法调用自 JS 事件(例如 onclick),而非调用自 Blazor 事件(例如 @onclick)。

如下示例中:

  • CallDotNetExample7 组件包含多个 ListItem2 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem2 组件由列表项消息 <span> 和第二个 <span> 组成,其中 display CSS 属性设置为 inline-block 以进行显示。
  • 当选择 ListItem2 组件列表项时,ListItem2UpdateMessage 方法会更改第一个 <span> 中的列表项文本,并通过将其 display 属性设置为 none 来隐藏第二个 <span>

以下 assignDotNetHelperJS 函数将 DotNetObjectReference 分配给名为 dotNetHelper 的属性中的元素:

<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }
</script>

以下 interopCallJS 函数对传递的元素使用 DotNetObjectReference 来调用名为 UpdateMessage 的 .NET 方法:

<script>
  window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem2 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。

每个 ListItem2 组件实例使用元素引用(列表项的第一个 <span> 元素)调用 OnAfterRenderAsync 中的 assignDotNetHelperJS 函数,组件实例作为 DotNetObjectReference

选择 ListItem2 组件的消息 <span> 时,将调用将 <span> 元素作为参数 (this) 传递的 interopCall,该参数将调用 UpdateMessage .NET 方法。 在 UpdateMessage 中,当设置 message 并更新第二个 <span>display 属性时,调用 StateHasChanged 来更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用该方法时是否应更新 UI。

在释放组件时释放 DotNetObjectReference

Shared/ListItem2.razor:

@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}

以下 CallDotNetExample7 父组件包括四个列表项,每个列表项都是 ListItem2 组件的一个实例。

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

Blazor WebAssembly 应用中的同步 JS 互操作

本部分仅适用于 应用。

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

如果你确定你的应用只在 Blazor WebAssembly 上运行,则可以选择执行同步 JS 互操作调用。 这比进行异步调用的开销略少,并且可能会导致呈现周期更少,因为在等待结果时没有中间状态。

若要在 Blazor WebAssembly 应用中进行从 JavaScript 到 .NET 的同步调用,请使用 DotNet.invokeMethod 而不是 DotNet.invokeMethodAsync

同步调用在以下情况下起作用:

  • 应用在 Blazor WebAssembly(而不是 Blazor Server)上运行。
  • 调用的函数以同步方式返回值。 该函数不是 async 方法,不会返回 .NET Task 或 JavaScript Promise

JavaScript 的位置

使用 JS 互操作概述文章中所述的任何方法加载 JavaScript (JS) 代码:

有关在 模块中隔离脚本的信息,请参阅 JavaScript 模块中的 JavaScript 隔离部分。

警告

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

JavaScript 模块中的 JavaScript 隔离

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

JS 隔离具有以下优势:

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

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

避免循环引用对象

不能在客户端上针对以下调用就包含循环引用的对象进行序列化:

  • .NET 方法调用。
  • 返回类型具有循环引用时,从 C# 发出的 JavaScript 方法调用。

字节数组支持

Blazor 支持优化的字节数组 JavaScript (JS) 互操作,这可以避免将字节数组编码/解码为 Base64。 以下示例使用 JS 互操作将字节数组传递给 .NET。

提供 sendByteArrayJS 函数。 该函数通过组件中的按钮进行调用,不返回值:

<script>
  window.sendByteArray = () => {
    const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
      0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
      0x20,0x43,0x61,0x70,0x74,0x69,0x61,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

Pages/CallDotNetExample8.razor:

@page "/call-dotnet-example-8"
@using System.Text

<h1>Call .NET Example 8</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

有关从 .NET 调用 JavaScript 时使用字节数组的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

从 JavaScript 流式传输到 .NET

Blazor 支持将数据直接从 JavaScript 流式传输到 .NET。 使用 Microsoft.JSInterop.IJSStreamReference 接口请求流。

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync 返回 Stream 并使用以下参数:

  • maxAllowedSize:JavaScript 中读取操作允许的最大字节数,如果未指定,则默认为 512,000 个字节。
  • cancellationTokenCancellationToken 用于取消读取。

在 JavaScript 中:

function streamToDotNet() {
  return new Uint8Array(10000000);
}

在 C# 代码中:

var dataReference = 
    await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream = 
    await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

在上面的示例中:

  • JS 是一个注入的 IJSRuntime 实例。 IJSRuntime 由 Blazor 框架注册。
  • dataReferenceStream 被写入磁盘 (file.txt) 的当前用户临时文件夹路径 (GetTempPath) 中。

在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数介绍了反向操作,即使用 DotNetStreamReference 从 .NET 流式传输到 JavaScript。

ASP.NET Core Blazor 文件上传介绍了如何在 Blazor 中上传文件。

对 JavaScript 互操作调用的大小限制

本部分仅适用于 Blazor Server应用。 在 Blazor WebAssembly 中,框架对 JavaScript (JS) 互操作输入和输出的大小不施加限制。

在 Blazor Server 中,JS 互操作调用的大小受中心方法允许的最大传入 SignalR 消息大小限制,该限制由 HubOptions.MaximumReceiveMessageSize(默认值:32 KB)强制执行。 JS 到 .NET 的 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。

如果未将 SignalR 日志记录设置为SignalR或跟踪,则消息大小错误仅显示在浏览器的开发人员工具控制台中:

错误:连接断开,出现错误“错误:服务器关闭时返回错误:连接因错误而关闭。”

服务器端日志记录设置为调试跟踪时,服务器端日志记录会针对消息大小错误显示

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

错误:

System.IO.InvalidDataException:超出了最大消息大小 32768B。 消息大小可以在 AddHubOptions 中配置。

通过在 Program.cs 中设置 MaximumReceiveMessageSize 来提高限制。 以下示例将最大接收消息大小设置为 64 KB:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

提高 SignalR 传入消息大小上限的代价是需要使用更多的服务器资源,这将使服务器面临来自恶意用户的更大风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。

在 Blazor Server 应用中开发在 JS 和 Blazor 之间传输大量数据的代码时,请考虑以下指南:

  • 利用本机流式处理互操作支持来传输大于 SignalR 传入消息大小限制的数据:
  • 常规提示:
    • 不要在 JS 和 C# 代码中分配大型对象。
    • 在进程完成或取消时释放已消耗的内存。
    • 为了安全起见,请强制执行以下附加要求:
      • 声明可以传递的最大文件或数据大小。
      • 声明从客户端到服务器的最低上传速率。
    • 在服务器收到数据后,数据可以:
      • 暂时存储在内存缓冲区中,直到收集完所有数据段。
      • 立即使用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。

组件处置期间的文档对象模型 (DOM) 清理任务

请勿在组件处置期间执行 DOM 清理任务的 JS 互操作代码。 相反,在客户端上使用采用 JavaScript 的 MutationObserver 模式,原因如下:

  • Dispose{Async} 中执行清理代码时,组件可能已从 DOM 中删除。
  • 在 Blazor Server 应用中,Blazor 呈现器可能已在 Dispose{Async} 中执行清理代码时由框架处置。

使用 MutationObserver 模式可以在从 DOM 中删除元素时运行函数。

无线路的 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 中删除元素时运行函数。

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

其他资源

有关如何从 .NET 调用 JS 函数的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数:

  • DotNet.invokeMethodAsync(推荐):对于 Blazor Server 和 Blazor WebAssembly 应用都是异步的。
  • DotNet.invokeMethod:仅对 Blazor WebAssembly 应用是同步的。

如下示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。
  • {ARGUMENTS} 占位符是要传递给该方法的以逗号分隔的可选参数,其中每个参数都必须是可执行 JSON 序列化的。
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync 返回表示操作结果的 JS PromiseDotNet.invokeMethod(仅限 Blazor WebAssembly)返回操作的结果。

重要

与同步版本 (invokeMethod) 相比,异步函数 (invokeMethodAsync) 是支持 Blazor Server 场景的首选。

.NET 方法必须是公共的静态方法,并且包含 [JSInvokable] 特性

如下示例中:

  • {<T>} 占位符指示返回类型,只有返回值的方法才需要返回类型。
  • {.NET METHOD ID} 占位符是方法标识符。
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

注意

静态 .NET 方法不支持调用开放式泛型方法,但实例方法支持该操作,本文稍后将对此进行介绍。

在下面的 CallDotNetExample1 组件中,ReturnArrayAsync C# 方法返回 int 数组。 [JSInvokable] 特性应用于该方法,这使得该方法可由 JS 调用。

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

<button> 元素的 onclick HTML 特性是 JavaScript 的 onclick 事件处理程序分配,用于处理 click 事件,而不是 Blazor 的 @onclick 指令属性。 returnArrayAsyncJS 函数被指定为处理程序。

以下 returnArrayAsyncJS 函数调用上述 CallDotNetExample1 组件的 ReturnArrayAsync .NET 方法,并将结果记录到浏览器的 Web 开发人员工具控制台。 BlazorSample 是应用的程序集名称。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

选择“Trigger .NET static method”按钮时,浏览器的开发人员工具控制台输出会显示数组数据。 各个浏览器的输出格式略有不同。 以下输出显示 Microsoft Edge 使用的格式:

Array(3) [ 1, 2, 3 ]

默认情况下,JS 调用的 .NET 方法标识符是 .NET 方法名称,但你可以使用 [JSInvokable] 特性 构造函数来指定其他标识符。 在以下示例中,DifferentMethodNameReturnArrayAsync 方法的指定方法标识符:

[JSInvokable("DifferentMethodName")]

在对 DotNet.invokeMethodAsyncDotNet.invokeMethod(仅限 Blazor WebAssembly)的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');(仅限 Blazor WebAssembly)

注意

本部分中的 ReturnArrayAsync 方法示例返回 Task 的结果,而不使用显式 C# asyncawait 关键字。 使用 asyncawait 对方法进行编码,是使用 await 关键字返回异步操作值的一种典型方法。

asyncawait 关键字组成的 ReturnArrayAsync 方法:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

有关详细信息,请参阅 C# 指南中的使用 Async 和 Await 的异步编程

创建 JavaScript 对象和数据引用以传递到 .NET

调用 DotNet.createJSObjectReference(jsObject) 以构造 JS 对象引用,以便可以将其传递到 .NET,其中 jsObject 是用于创建 JS 对象引用的 JS 对象。 以下示例将对不可序列化的 window 对象的引用传递给 .NET,后者在 ReceiveWindowObject C# 方法中接收它作为 IJSObjectReference

DotNet.invokeMethodAsync('{APP NAMESPACE}', 'ReceiveWindowObject', 
  DotNet.createJSObjectReference(window));
[JSInvokable]
public void ReceiveWindowObject(IJSObjectReference objRef)
{
    ...
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

调用 DotNet.createJSStreamReference(streamReference) 来构造 JS 流引用,以便可以传递给 .NET,其中 streamReferenceArrayBufferBlob 或任何 类型化数组(例如 Uint8ArrayFloat32Array),用于创建 JS 流引用。

调用实例 .NET 方法

若要从 JavaScript (JS) 调用实例 .NET 方法,请执行以下操作:

  • 通过将实例包装在 DotNetObjectReference 中并对其调用 Create,将 .NET 实例通过引用传递给 JS。
  • 使用传递的 DotNetObjectReference 中的 invokeMethodAsyncinvokeMethod(仅限 Blazor WebAssembly)从 JS 调用 .NET 实例方法。 在从 JS 调用其他 .NET 方法时,也可以将 .NET 实例作为参数传递。
  • 释放 DotNetObjectReference

本文的以下部分演示了调用实例 .NET 方法的各种方法:

组件实例示例

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMessage .NET 方法。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

对于以下 CallDotNetExample2 组件:

  • 组件有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage
  • 选择“Trigger .NET instance method”按钮后,会使用 DotNetObjectReference 调用 JS 函数 sayHello1
  • sayHello1:
    • 调用 GetHelloMessage 并接收消息结果。
    • 将消息结果返回给进行调用的 TriggerDotNetInstanceMethod 方法。
  • 向用户显示 result 中从 sayHello1 返回的消息。
  • 为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

若要向实例方法传递参数,请执行以下操作:

  1. 向 .NET 方法调用添加参数。 在下面的示例中,一个名称被传递给方法。 根据需要将其他参数添加到列表。

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    
  2. 向 .NET 方法提供参数列表。

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

类实例示例

以下 sayHello1JS 函数:

  • 对传递的 DotNetObjectReference 调用 GetHelloMessage .NET 方法。
  • 将消息从 GetHelloMessage 返回给 sayHello1 调用方。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

以下 HelloHelper 类有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage。 创建 HelloHelper 时,Name 属性中的名称用于从 GetHelloMessage 返回消息。

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}

以下 JsInteropClasses3 类中的 CallHelloHelperGetHelloMessage 方法使用一个新实例 HelloHelper 调用 JS 函数 sayHello1

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

    public JsInteropClasses3(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

在以下 CallDotNetExample4 组件中选择“Trigger .NET instance method”按钮后,将使用值 name 调用 JsInteropClasses3.CallHelloHelperGetHelloMessage

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    }
}

下图显示了在 Name 字段中具有名称 Amy Pond 的呈现组件。 选择该按钮后,UI 中将显示 Hello, Amy Pond!

呈现的“CallDotNetExample4”组件示例

还可以在组件中完全实现上述 JsInteropClasses3 类中所示的模式。

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

CallDotNetExample5 组件显示的输出是在 name 字段中提供名称 Amy Pond 时的 Hello, Amy Pond!

在前面的 CallDotNetExample5 组件中,释放了 .NET 对象引用。 如果某个类或组件没有释放 DotNetObjectReference,则通过对传递的 DotNetObjectReference 调用 dispose 从客户端对其进行释放:

window.{JS FUNCTION NAME} = (dotNetHelper) => {
  dotNetHelper.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}');
  dotNetHelper.dispose();
}

在上面的示例中:

  • {JS FUNCTION NAME} 占位符是 JS 函数的名称。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

组件实例 .NET 方法帮助程序类

帮助程序类可以将 .NET 实例方法作为 Action 进行调用。 帮助程序类在以下情况下很有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。

如下示例中:

  • CallDotNetExample6 组件包含多个 ListItem1 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem1 组件都由一个消息和一个按钮组成。
  • 选择 ListItem1 组件按钮后,ListItem1UpdateMessage 方法会更改列表项文本并隐藏该按钮。

以下 MessageUpdateInvokeHelper 类维护一个可进行 JS 调用的 .NET 方法 UpdateMessageCaller,以调用在实例化类时指定的 ActionBlazorSample 是应用的程序集名称。

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

以下 updateMessageCallerJS 函数调用 UpdateMessageCaller .NET 方法。 BlazorSample 是应用的程序集名称。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.updateMessageCaller = (dotNetHelper) => {
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  }
</script>

下面的 ListItem1 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。 每个 ListItem1 组件实例都建立了一个 MessageUpdateInvokeHelper 的实例,其中 Action 设置为其 UpdateMessage 方法。

选择 ListItem1 组件的“InteropCall”按钮后,就会使用为 MessageUpdateInvokeHelper 实例创建的 DotNetObjectReference 调用 updateMessageCaller。 这允许框架对该 ListItem1MessageUpdateInvokeHelper 实例调用 UpdateMessageCaller。 传递的 DotNetObjectReference 在 JS (dotNetHelper.dispose()) 中被释放。

Shared/ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

StateHasChanged 被调用,以在 UpdateMessage 中设置 message 时更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用 Action 时是否应更新 UI。

以下 CallDotNetExample6 父组件包括四个列表项,每个列表项都是 ListItem1 组件的一个实例。

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

下图显示了在选择第二个“InteropCall”按钮后呈现的 CallDotNetExample6 父组件:

  • 第二个 ListItem1 组件已显示 UpdateMessage Called! 消息。
  • 第二个 ListItem1 组件的“InteropCall”按钮不可见,因为该按钮的 CSS display 属性被设置为 none

呈现的“CallDotNetExample6”组件示例

从分配给元素属性的 DotNetObjectReference 调用的组件实例 .NET 方法

DotNetObjectReference 分配给 HTML 元素的属性允许在组件实例上调用 .NET 方法:

组件实例 .NET 方法帮助程序类部分中描述的方法类似,此方法在以下场景中非常有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。
  • .NET 方法调用自 JS 事件(例如 onclick),而非调用自 Blazor 事件(例如 @onclick)。

如下示例中:

  • CallDotNetExample7 组件包含多个 ListItem2 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem2 组件由列表项消息 <span> 和第二个 <span> 组成,其中 display CSS 属性设置为 inline-block 以进行显示。
  • 当选择 ListItem2 组件列表项时,ListItem2UpdateMessage 方法会更改第一个 <span> 中的列表项文本,并通过将其 display 属性设置为 none 来隐藏第二个 <span>

以下 assignDotNetHelperJS 函数将 DotNetObjectReference 分配给名为 dotNetHelper 的属性中的元素:

<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }
</script>

以下 interopCallJS 函数对传递的元素使用 DotNetObjectReference 来调用名为 UpdateMessage 的 .NET 方法:

<script>
  window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem2 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。

每个 ListItem2 组件实例使用元素引用(列表项的第一个 <span> 元素)调用 OnAfterRenderAsync 中的 assignDotNetHelperJS 函数,组件实例作为 DotNetObjectReference

选择 ListItem2 组件的消息 <span> 时,将调用将 <span> 元素作为参数 (this) 传递的 interopCall,该参数将调用 UpdateMessage .NET 方法。 在 UpdateMessage 中,当设置 message 并更新第二个 <span>display 属性时,调用 StateHasChanged 来更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用该方法时是否应更新 UI。

在释放组件时释放 DotNetObjectReference

Shared/ListItem2.razor:

@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}

以下 CallDotNetExample7 父组件包括四个列表项,每个列表项都是 ListItem2 组件的一个实例。

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

JavaScript 的位置

使用 JS 互操作概述文章中所述的任何方法加载 JavaScript (JS) 代码:

有关在 模块中隔离脚本的信息,请参阅 JavaScript 模块中的 JavaScript 隔离部分。

警告

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

避免循环引用对象

不能在客户端上针对以下调用就包含循环引用的对象进行序列化:

  • .NET 方法调用。
  • 返回类型具有循环引用时,从 C# 发出的 JavaScript 方法调用。

对 JavaScript 互操作调用的大小限制

本部分仅适用于 Blazor Server应用。 在 Blazor WebAssembly 中,框架对 JavaScript (JS) 互操作输入和输出的大小不施加限制。

在 Blazor Server 中,JS 互操作调用的大小受中心方法允许的最大传入 SignalR 消息大小限制,该限制由 HubOptions.MaximumReceiveMessageSize(默认值:32 KB)强制执行。 JS 到 .NET 的 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。

如果未将 SignalR 日志记录设置为SignalR或跟踪,则消息大小错误仅显示在浏览器的开发人员工具控制台中:

错误:连接断开,出现错误“错误:服务器关闭时返回错误:连接因错误而关闭。”

服务器端日志记录设置为调试跟踪时,服务器端日志记录会针对消息大小错误显示

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

错误:

System.IO.InvalidDataException:超出了最大消息大小 32768B。 消息大小可以在 AddHubOptions 中配置。

通过在 Startup.ConfigureServices 中设置 MaximumReceiveMessageSize 来提高限制:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

提高 SignalR 传入消息大小上限的代价是需要使用更多的服务器资源,这将使服务器面临来自恶意用户的更大风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。

读取大型有效负载的一种方法是以较小区块发送内容,并将有效负载作为 Stream 处理。 在读取大型 JSON 有效负载,或者数据在 JS 中以原始字节形式提供时,可使用此方法。 有关演示如何使用类似于 Blazor Server的方法在 Blazor Server 中发送大型二进制有效负载的示例,请参阅InputFile

注意

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

在 Blazor Server 应用中开发在 JS 和 Blazor 之间传输大量数据的代码时,请考虑以下指南:

  • 将数据切成小块,然后按顺序发送数据段,直到服务器收到所有数据。
  • 不要在 JS 和 C# 代码中分配大型对象。
  • 发送或接收数据时,请勿长时间阻止主 UI 线程。
  • 在进程完成或取消时释放已消耗的内存。
  • 为了安全起见,请强制执行以下附加要求:
    • 声明可以传递的最大文件或数据大小。
    • 声明从客户端到服务器的最低上传速率。
  • 在服务器收到数据后,数据可以:
    • 暂时存储在内存缓冲区中,直到收集完所有数据段。
    • 立即使用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。

JavaScript 模块中的 JavaScript 隔离

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

JS 隔离具有以下优势:

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

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

组件处置期间的文档对象模型 (DOM) 清理任务

请勿在组件处置期间执行 DOM 清理任务的 JS 互操作代码。 相反,在客户端上使用采用 JavaScript 的 MutationObserver 模式,原因如下:

  • Dispose{Async} 中执行清理代码时,组件可能已从 DOM 中删除。
  • 在 Blazor Server 应用中,Blazor 呈现器可能已在 Dispose{Async} 中执行清理代码时由框架处置。

使用 MutationObserver 模式可以在从 DOM 中删除元素时运行函数。

无线路的 JavaScript 互操作调用

本部分仅适用于 Blazor Server应用。

在 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 中删除元素时运行函数。

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

其他资源

有关如何从 .NET 调用 JS 函数的信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数:

  • DotNet.invokeMethodAsync(推荐):对于 Blazor Server 和 Blazor WebAssembly 应用都是异步的。
  • DotNet.invokeMethod:仅对 Blazor WebAssembly 应用是同步的。

如下示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。
  • {ARGUMENTS} 占位符是要传递给该方法的以逗号分隔的可选参数,其中每个参数都必须是可执行 JSON 序列化的。
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync 返回表示操作结果的 JS PromiseDotNet.invokeMethod(仅限 Blazor WebAssembly)返回操作的结果。

重要

与同步版本 (invokeMethod) 相比,异步函数 (invokeMethodAsync) 是支持 Blazor Server 场景的首选。

.NET 方法必须是公共的静态方法,并且包含 [JSInvokable] 特性

如下示例中:

  • {<T>} 占位符指示返回类型,只有返回值的方法才需要返回类型。
  • {.NET METHOD ID} 占位符是方法标识符。
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

注意

静态 .NET 方法不支持调用开放式泛型方法,但实例方法支持该操作,本文稍后将对此进行介绍。

在下面的 CallDotNetExample1 组件中,ReturnArrayAsync C# 方法返回 int 数组。 [JSInvokable] 特性应用于该方法,这使得该方法可由 JS 调用。

Pages/CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

<button> 元素的 onclick HTML 特性是 JavaScript 的 onclick 事件处理程序分配,用于处理 click 事件,而不是 Blazor 的 @onclick 指令属性。 returnArrayAsyncJS 函数被指定为处理程序。

以下 returnArrayAsyncJS 函数调用上述 CallDotNetExample1 组件的 ReturnArrayAsync .NET 方法,并将结果记录到浏览器的 Web 开发人员工具控制台。 BlazorSample 是应用的程序集名称。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

选择“Trigger .NET static method”按钮时,浏览器的开发人员工具控制台输出会显示数组数据。 各个浏览器的输出格式略有不同。 以下输出显示 Microsoft Edge 使用的格式:

Array(3) [ 1, 2, 3 ]

默认情况下,JS 调用的 .NET 方法标识符是 .NET 方法名称,但你可以使用 [JSInvokable] 特性 构造函数来指定其他标识符。 在以下示例中,DifferentMethodNameReturnArrayAsync 方法的指定方法标识符:

[JSInvokable("DifferentMethodName")]

在对 DotNet.invokeMethodAsyncDotNet.invokeMethod(仅限 Blazor WebAssembly)的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');(仅限 Blazor WebAssembly)

注意

本部分中的 ReturnArrayAsync 方法示例返回 Task 的结果,而不使用显式 C# asyncawait 关键字。 使用 asyncawait 对方法进行编码,是使用 await 关键字返回异步操作值的一种典型方法。

asyncawait 关键字组成的 ReturnArrayAsync 方法:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

有关详细信息,请参阅 C# 指南中的使用 Async 和 Await 的异步编程

创建 JavaScript 对象和数据引用以传递到 .NET

调用 DotNet.createJSObjectReference(jsObject) 以构造 JS 对象引用,以便可以将其传递到 .NET,其中 jsObject 是用于创建 JS 对象引用的 JS 对象。 以下示例将对不可序列化的 window 对象的引用传递给 .NET,后者在 ReceiveWindowObject C# 方法中接收它作为 IJSObjectReference

DotNet.invokeMethodAsync('{APP NAMESPACE}', 'ReceiveWindowObject', 
  DotNet.createJSObjectReference(window));
[JSInvokable]
public void ReceiveWindowObject(IJSObjectReference objRef)
{
    ...
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

调用 DotNet.createJSStreamReference(streamReference) 来构造 JS 流引用,以便可以传递给 .NET,其中 streamReferenceArrayBufferBlob 或任何 类型化数组(例如 Uint8ArrayFloat32Array),用于创建 JS 流引用。

调用实例 .NET 方法

若要从 JavaScript (JS) 调用实例 .NET 方法,请执行以下操作:

  • 通过将实例包装在 DotNetObjectReference 中并对其调用 Create,将 .NET 实例通过引用传递给 JS。
  • 使用传递的 DotNetObjectReference 中的 invokeMethodAsyncinvokeMethod(仅限 Blazor WebAssembly)从 JS 调用 .NET 实例方法。 在从 JS 调用其他 .NET 方法时,也可以将 .NET 实例作为参数传递。
  • 释放 DotNetObjectReference

本文的以下部分演示了调用实例 .NET 方法的各种方法:

组件实例示例

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMessage .NET 方法。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

对于以下 CallDotNetExample2 组件:

  • 组件有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage
  • 选择“Trigger .NET instance method”按钮后,会使用 DotNetObjectReference 调用 JS 函数 sayHello1
  • sayHello1:
    • 调用 GetHelloMessage 并接收消息结果。
    • 将消息结果返回给进行调用的 TriggerDotNetInstanceMethod 方法。
  • 向用户显示 result 中从 sayHello1 返回的消息。
  • 为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

Pages/CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

若要向实例方法传递参数,请执行以下操作:

  1. 向 .NET 方法调用添加参数。 在下面的示例中,一个名称被传递给方法。 根据需要将其他参数添加到列表。

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    
  2. 向 .NET 方法提供参数列表。

    Pages/CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

类实例示例

以下 sayHello1JS 函数:

  • 对传递的 DotNetObjectReference 调用 GetHelloMessage .NET 方法。
  • 将消息从 GetHelloMessage 返回给 sayHello1 调用方。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

以下 HelloHelper 类有一个可以进行 JS 调用的 .NET 方法,名为 GetHelloMessage。 创建 HelloHelper 时,Name 属性中的名称用于从 GetHelloMessage 返回消息。

HelloHelper.cs:

using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}

以下 JsInteropClasses3 类中的 CallHelloHelperGetHelloMessage 方法使用一个新实例 HelloHelper 调用 JS 函数 sayHello1

JsInteropClasses3.cs:

using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

    public JsInteropClasses3(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

在以下 CallDotNetExample4 组件中选择“Trigger .NET instance method”按钮后,将使用值 name 调用 JsInteropClasses3.CallHelloHelperGetHelloMessage

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    }
}

下图显示了在 Name 字段中具有名称 Amy Pond 的呈现组件。 选择该按钮后,UI 中将显示 Hello, Amy Pond!

呈现的“CallDotNetExample4”组件示例

还可以在组件中完全实现上述 JsInteropClasses3 类中所示的模式。

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

为了避免内存泄漏并允许垃圾回收,当 DotNetObjectReference 创建的 .NET 对象引用超出 using var 语法范围时,将释放该对象引用。

CallDotNetExample5 组件显示的输出是在 name 字段中提供名称 Amy Pond 时的 Hello, Amy Pond!

在前面的 CallDotNetExample5 组件中,释放了 .NET 对象引用。 如果某个类或组件没有释放 DotNetObjectReference,则通过对传递的 DotNetObjectReference 调用 dispose 从客户端对其进行释放:

window.{JS FUNCTION NAME} = (dotNetHelper) => {
  dotNetHelper.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}');
  dotNetHelper.dispose();
}

在上面的示例中:

  • {JS FUNCTION NAME} 占位符是 JS 函数的名称。
  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

组件实例 .NET 方法帮助程序类

帮助程序类可以将 .NET 实例方法作为 Action 进行调用。 帮助程序类在以下情况下很有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。

如下示例中:

  • CallDotNetExample6 组件包含多个 ListItem1 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem1 组件都由一个消息和一个按钮组成。
  • 选择 ListItem1 组件按钮后,ListItem1UpdateMessage 方法会更改列表项文本并隐藏该按钮。

以下 MessageUpdateInvokeHelper 类维护一个可进行 JS 调用的 .NET 方法 UpdateMessageCaller,以调用在实例化类时指定的 ActionBlazorSample 是应用的程序集名称。

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable("BlazorSample")]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

以下 updateMessageCallerJS 函数调用 UpdateMessageCaller .NET 方法。 BlazorSample 是应用的程序集名称。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 的结束 </body> 标记内:

<script>
  window.updateMessageCaller = (dotNetHelper) => {
    dotNetHelper.invokeMethodAsync('BlazorSample', 'UpdateMessageCaller');
    dotNetHelper.dispose();
  }
</script>

下面的 ListItem1 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。 每个 ListItem1 组件实例都建立了一个 MessageUpdateInvokeHelper 的实例,其中 Action 设置为其 UpdateMessage 方法。

选择 ListItem1 组件的“InteropCall”按钮后,就会使用为 MessageUpdateInvokeHelper 实例创建的 DotNetObjectReference 调用 updateMessageCaller。 这允许框架对该 ListItem1MessageUpdateInvokeHelper 实例调用 UpdateMessageCaller。 传递的 DotNetObjectReference 在 JS (dotNetHelper.dispose()) 中被释放。

Shared/ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

StateHasChanged 被调用,以在 UpdateMessage 中设置 message 时更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用 Action 时是否应更新 UI。

以下 CallDotNetExample6 父组件包括四个列表项,每个列表项都是 ListItem1 组件的一个实例。

Pages/CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

下图显示了在选择第二个“InteropCall”按钮后呈现的 CallDotNetExample6 父组件:

  • 第二个 ListItem1 组件已显示 UpdateMessage Called! 消息。
  • 第二个 ListItem1 组件的“InteropCall”按钮不可见,因为该按钮的 CSS display 属性被设置为 none

呈现的“CallDotNetExample6”组件示例

从分配给元素属性的 DotNetObjectReference 调用的组件实例 .NET 方法

DotNetObjectReference 分配给 HTML 元素的属性允许在组件实例上调用 .NET 方法:

组件实例 .NET 方法帮助程序类部分中描述的方法类似,此方法在以下场景中非常有用:

  • 同一类型的多个组件呈现在同一页上。
  • 在 Blazor Server 应用中,其中多个用户同时使用同一组件。
  • .NET 方法调用自 JS 事件(例如 onclick),而非调用自 Blazor 事件(例如 @onclick)。

如下示例中:

  • CallDotNetExample7 组件包含多个 ListItem2 组件,它是应用的 Shared 文件夹中的一个共享组件。
  • 每个 ListItem2 组件由列表项消息 <span> 和第二个 <span> 组成,其中 display CSS 属性设置为 inline-block 以进行显示。
  • 当选择 ListItem2 组件列表项时,ListItem2UpdateMessage 方法会更改第一个 <span> 中的列表项文本,并通过将其 display 属性设置为 none 来隐藏第二个 <span>

以下 assignDotNetHelperJS 函数将 DotNetObjectReference 分配给名为 dotNetHelper 的属性中的元素:

<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }
</script>

以下 interopCallJS 函数对传递的元素使用 DotNetObjectReference 来调用名为 UpdateMessage 的 .NET 方法:

<script>
  window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  }
</script>

注意

有关生产应用的 JS 位置和建议的一般指南,请参阅 ASP.NET Core Blazor JavaScript 互操作性(JS 互操作)

在上一示例中,变量名称 dotNetHelper 是任意的,可更改为任何首选名称。

下面的 ListItem2 组件是一个共享组件,可在父组件中使用任意次,为一个 HTML 列表(<ul>...</ul><ol>...</ol>)创建列表项 (<li>...</li>)。

每个 ListItem2 组件实例使用元素引用(列表项的第一个 <span> 元素)调用 OnAfterRenderAsync 中的 assignDotNetHelperJS 函数,组件实例作为 DotNetObjectReference

选择 ListItem2 组件的消息 <span> 时,将调用将 <span> 元素作为参数 (this) 传递的 interopCall,该参数将调用 UpdateMessage .NET 方法。 在 UpdateMessage 中,当设置 message 并更新第二个 <span>display 属性时,调用 StateHasChanged 来更新 UI。 如果不调用 StateHasChanged,则 Blazor 无法判断在调用该方法时是否应更新 UI。

在释放组件时释放 DotNetObjectReference

Shared/ListItem2.razor:

@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}

以下 CallDotNetExample7 父组件包括四个列表项,每个列表项都是 ListItem2 组件的一个实例。

Pages/CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

JavaScript 的位置

使用 JS 互操作概述文章中所述的任何方法加载 JavaScript (JS) 代码:

警告

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

避免循环引用对象

不能在客户端上针对以下调用就包含循环引用的对象进行序列化:

  • .NET 方法调用。
  • 返回类型具有循环引用时,从 C# 发出的 JavaScript 方法调用。

对 JavaScript 互操作调用的大小限制

本部分仅适用于 Blazor Server应用。 在 Blazor WebAssembly 中,框架对 JavaScript (JS) 互操作输入和输出的大小不施加限制。

在 Blazor Server 中,JS 互操作调用的大小受中心方法允许的最大传入 SignalR 消息大小限制,该限制由 HubOptions.MaximumReceiveMessageSize(默认值:32 KB)强制执行。 JS 到 .NET 的 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。

如果未将 SignalR 日志记录设置为SignalR或跟踪,则消息大小错误仅显示在浏览器的开发人员工具控制台中:

错误:连接断开,出现错误“错误:服务器关闭时返回错误:连接因错误而关闭。”

服务器端日志记录设置为调试跟踪时,服务器端日志记录会针对消息大小错误显示

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

错误:

System.IO.InvalidDataException:超出了最大消息大小 32768B。 消息大小可以在 AddHubOptions 中配置。

通过在 Startup.ConfigureServices 中设置 MaximumReceiveMessageSize 来提高限制:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

提高 SignalR 传入消息大小上限的代价是需要使用更多的服务器资源,这将使服务器面临来自恶意用户的更大风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。

读取大型有效负载的一种方法是以较小区块发送内容,并将有效负载作为 Stream 处理。 在读取大型 JSON 有效负载,或者数据在 JS 中以原始字节形式提供时,可使用此方法。 有关演示如何使用类似于 Blazor Server的方法在 Blazor Server 中发送大型二进制有效负载的示例,请参阅InputFile

注意

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

在 Blazor Server 应用中开发在 JS 和 Blazor 之间传输大量数据的代码时,请考虑以下指南:

  • 将数据切成小块,然后按顺序发送数据段,直到服务器收到所有数据。
  • 不要在 JS 和 C# 代码中分配大型对象。
  • 发送或接收数据时,请勿长时间阻止主 UI 线程。
  • 在进程完成或取消时释放已消耗的内存。
  • 为了安全起见,请强制执行以下附加要求:
    • 声明可以传递的最大文件或数据大小。
    • 声明从客户端到服务器的最低上传速率。
  • 在服务器收到数据后,数据可以:
    • 暂时存储在内存缓冲区中,直到收集完所有数据段。
    • 立即使用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。

组件处置期间的文档对象模型 (DOM) 清理任务

请勿在组件处置期间执行 DOM 清理任务的 JS 互操作代码。 相反,在客户端上使用采用 JavaScript 的 MutationObserver 模式,原因如下:

  • Dispose{Async} 中执行清理代码时,组件可能已从 DOM 中删除。
  • 在 Blazor Server 应用中,Blazor 呈现器可能已在 Dispose{Async} 中执行清理代码时由框架处置。

使用 MutationObserver 模式可以在从 DOM 中删除元素时运行函数。

无线路的 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 中删除元素时运行函数。

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

其他资源