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

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

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

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数 DotNet.invokeMethodDotNet.invokeMethodAsync。 传入包含该方法的程序集的名称、静态 .NET 方法的标识符以及任意自变量。

如下示例中:

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

DotNet.invokeMethod 返回操作的结果。 DotNet.invokeMethodAsync 返回表示操作结果的 JS Promise

与同步版本 (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 是应用的程序集名称。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.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.invokeMethodDotNet.invokeMethodAsync 的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');

注意

本部分中的 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 的异步编程

调用实例 .NET 方法

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

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

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

DotNetObjectReference 传递到单个 JavaScript 函数

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

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

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

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

在上一示例中,变量名称 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>? dotNetHelper;

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

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

    public void Dispose()
    {
        dotNetHelper?.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>? dotNetHelper;
    
        public async Task TriggerDotNetInstanceMethod()
        {
            dotNetHelper = DotNetObjectReference.Create(this);
            result = await JS.InvokeAsync<string>("sayHello2", dotNetHelper, name);
        }
    
        [JSInvokable]
        public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";
    
        public void Dispose()
        {
            dotNetHelper?.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();
    }
}

在上面的示例中:

  • 变量名称 dotNetHelper 是任意的,可更改为任何首选名称。
  • 组件必须显式释放 DotNetObjectReference 以允许垃圾回收并防止内存泄漏。

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

<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>

在上面的示例中:

  • 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}");
    }
}

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 的结束 </body> 标记内,添加以下 <script> 块。

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>

在下面的 GenericsExample 组件中:

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

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@inject IJSRuntime JSRuntime

<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 };

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

        await JSRuntime.InvokeVoidAsync(
            "invokeMethodsAsync",
            syncInterop,
            DotNetObjectReference.Create(genericType1),
            DotNetObjectReference.Create(genericType2));
    }
}

下面演示了在 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 调用方。

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

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

在上一示例中,变量名称 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 System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable
{
    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper>? dotNetHelper;

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

    public ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        dotNetHelper = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", dotNetHelper);
    }

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

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@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;

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

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

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

Rendered 'CallDotNetExample4' component example

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

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@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;
    private DotNetObjectReference<HelloHelper>? dotNetHelper;

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

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

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

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

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

在上面的示例中:

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

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

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

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

如下示例中:

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

以下 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/_Layout.cshtml (Blazor Server) 的结束 </body> 标记内:

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

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

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

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

Shared/ListItem.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 父组件包括四个列表项,每个列表项都是 ListItem 组件的一个实例。

Pages/CallDotNetExample6.razor:

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

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

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

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

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

Rendered 'CallDotNetExample6' component example

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 支持优化的字节数组 JS 互操作,这可以避免将字节数组编码/解码为 Base64。 以下示例使用 JS 互操作将字节数组传递给 .NET。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Layout.cshtml (Blazor Server) 的结束 </body> 标记内,提供一个 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>

Pages/CallDotNetExample7.razor:

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

<h1>Call .NET Example 7</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 实例。
  • 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 (64 * 1024):

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

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

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

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

其他资源

  • 从 ASP.NET Core 中的 .NET 方法调用 JavaScript 函数
  • 示例(dotnet/AspNetCore GitHub 存储库 main 分支):main 分支表示产品单元针对下一个 ASP.NET Core 版本的当前开发。 要为其他版本(例如 release/5.0)选择分支,请使用“切换分支或标记”下拉列表来选择分支。
  • 与文档对象模型 (DOM) 交互

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

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数 DotNet.invokeMethodDotNet.invokeMethodAsync。 传入包含该方法的程序集的名称、静态 .NET 方法的标识符以及任意自变量。

如下示例中:

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

DotNet.invokeMethod 返回操作的结果。 DotNet.invokeMethodAsync 返回表示操作结果的 JS Promise

与同步版本 (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.invokeMethodDotNet.invokeMethodAsync 的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');

注意

本部分中的 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 的异步编程

调用实例 .NET 方法

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

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

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

组件实例示例

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMethod .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;

    public async Task TriggerDotNetInstanceMethod()
    {
        objRef = DotNetObjectReference.Create(this);
        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;
    
        public async Task TriggerDotNetInstanceMethod()
        {
            objRef = DotNetObjectReference.Create(this);
            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 System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable
{
    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper> objRef;

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

    public ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    }

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@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;

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

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

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

Rendered 'CallDotNetExample4' component example

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

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@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;
    private DotNetObjectReference<HelloHelper> objRef;

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

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

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

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

在上面的示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

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

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

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

如下示例中:

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

以下 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>

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

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

Shared/ListItem.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 父组件包括四个列表项,每个列表项都是 ListItem 组件的一个实例。

Pages/CallDotNetExample6.razor:

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

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

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

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

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

Rendered 'CallDotNetExample6' component example

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 存储库对版本分支名称使用“release/{VERSION}”格式,其中 {VERSION} 占位符是发布版本。 例如,为 ASP.NET Core 6.0 版本选择 release/6.0 分支。 其他参考源存储库可能使用不同的版本标记命名方案。

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

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

JavaScript 模块中的 JavaScript 隔离

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

JS 隔离具有以下优势:

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

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

其他资源

  • 从 ASP.NET Core 中的 .NET 方法调用 JavaScript 函数
  • 示例(dotnet/AspNetCore GitHub 存储库 main 分支):main 分支表示产品单元针对下一个 ASP.NET Core 版本的当前开发。 要为其他版本(例如 release/5.0)选择分支,请使用“切换分支或标记”下拉列表来选择分支。
  • 与文档对象模型 (DOM) 交互

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

调用静态 .NET 方法

若要从 JavaScript (JS) 调用静态 .NET 方法,可以使用 JS 函数 DotNet.invokeMethodDotNet.invokeMethodAsync。 传入包含该方法的程序集的名称、静态 .NET 方法的标识符以及任意自变量。

如下示例中:

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

DotNet.invokeMethod 返回操作的结果。 DotNet.invokeMethodAsync 返回表示操作结果的 JS Promise

与同步版本 (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.invokeMethodDotNet.invokeMethodAsync 的调用中,调用 DifferentMethodName 以执行 ReturnArrayAsync .NET 方法:

  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');

注意

本部分中的 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 的异步编程

调用实例 .NET 方法

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

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

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

组件实例示例

以下 sayHello1JS 函数接收 DotNetObjectReference 并调用 invokeMethodAsync 以调用组件的 GetHelloMethod .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;

    public async Task TriggerDotNetInstanceMethod()
    {
        objRef = DotNetObjectReference.Create(this);
        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;
    
        public async Task TriggerDotNetInstanceMethod()
        {
            objRef = DotNetObjectReference.Create(this);
            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 System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3 : IDisposable
{
    private readonly IJSRuntime js;
    private DotNetObjectReference<HelloHelper> objRef;

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

    public ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        objRef = DotNetObjectReference.Create(new HelloHelper(name));

        return js.InvokeAsync<string>("sayHello1", objRef);
    }

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

Pages/CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@implements IDisposable
@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;

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

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

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

Rendered 'CallDotNetExample4' component example

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

Pages/CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@implements IDisposable
@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;
    private DotNetObjectReference<HelloHelper> objRef;

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

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

为避免内存泄漏并允许进行垃圾回收,在 Dispose 方法中释放了由 DotNetObjectReference 创建的 .NET 对象引用。

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

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

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

在上面的示例中:

  • {ASSEMBLY NAME} 占位符是应用的程序集名称。
  • {.NET METHOD ID} 占位符是 .NET 方法标识符。

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

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

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

如下示例中:

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

以下 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>

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

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

Shared/ListItem.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 父组件包括四个列表项,每个列表项都是 ListItem 组件的一个实例。

Pages/CallDotNetExample6.razor:

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

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

<ul>
    <ListItem />
    <ListItem />
    <ListItem />
    <ListItem />
</ul>

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

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

Rendered 'CallDotNetExample6' component example

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 存储库对版本分支名称使用“release/{VERSION}”格式,其中 {VERSION} 占位符是发布版本。 例如,为 ASP.NET Core 6.0 版本选择 release/6.0 分支。 其他参考源存储库可能使用不同的版本标记命名方案。

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

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

其他资源

  • 从 ASP.NET Core 中的 .NET 方法调用 JavaScript 函数
  • 示例(dotnet/AspNetCore GitHub 存储库 main 分支):main 分支表示产品单元针对下一个 ASP.NET Core 版本的当前开发。 要为其他版本(例如 release/5.0)选择分支,请使用“切换分支或标记”下拉列表来选择分支。
  • 与文档对象模型 (DOM) 交互