Вызов методов .NET из функций JavaScript в ASP.NET Core Blazor

В этой статье рассматривается вызов методов .NET из JavaScript (JS). Сведения о том, как вызывать функции JS из .NET, см. здесь: Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Вызов статического метода .NET

Чтобы вызвать статический метод .NET из JavaScript (JS), используйте функции JS (DotNet.invokeMethod или DotNet.invokeMethodAsync). Передайте имя сборки, содержащей метод, идентификатор статического метода .NET и любые аргументы.

В следующем примере:

  • Заполнитель {ASSEMBLY NAME} — это имя сборки приложения.
  • Заполнитель {.NET METHOD ID} является идентификатором метода .NET.
  • Заполнитель {ARGUMENTS} необязателен. Аргументы, разделенные запятыми, передаются в метод, каждый из которых должен сериализоваться в JSON.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

Объект DotNet.invokeMethod возвращает результат операции. Объект DotNet.invokeMethodAsync возвращает значение типа JS Promise, представляющее результат операции.

Асинхронная функция (invokeMethodAsync) является предпочтительной по сравнению с синхронной версией (invokeMethod) для поддержки сценариев Blazor Server.

Метод .NET должен быть открытым, статическим и иметь атрибут [JSInvokable].

В следующем примере:

  • Заполнитель {<T>} указывает тип возвращаемого значения, который требуется только для методов, возвращающих значение.
  • Заполнитель {.NET METHOD ID} является идентификатором метода.
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Вызов открытых универсальных методов не поддерживается со статическими методами .NET, но поддерживается с методами экземпляров, которые описаны далее в этой статье.

В следующем компоненте CallDotNetExample1 метод C# ReturnArrayAsync возвращает массив 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 });
    }
}

HTML-атрибут onclick элемента <button> — это назначение обработчика событий JavaScript onclick для обработки событий click, а не атрибут @onclick директивы Blazor. Функция returnArrayAsync JS назначается в качестве обработчика.

Следующая функция returnArrayAsync JS вызывает метод .NET ReturnArrayAsync предыдущего компонента CallDotNetExample1 и записывает результат в консоль веб-средств разработчика браузера. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Layout.cshtml (Blazor Server):

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

При нажатии кнопки Trigger .NET static method в выходных данных консоли средств разработчика в браузере отображаются данные массива. Формат выходных данных незначительно различается в разных браузерах. Следующий результат имеет формат, используемый Microsoft Edge.

Array(3) [ 1, 2, 3 ]

По умолчанию идентификатором метода .NET для вызова JS является имя метода .NET, но можно указать другой идентификатор с помощью конструктора атрибута [JSInvokable]. В следующем примере DifferentMethodName — это назначенный идентификатор для метода ReturnArrayAsync:

[JSInvokable("DifferentMethodName")]

В вызове DotNet.invokeMethod или DotNet.invokeMethodAsync вызовите метод DifferentMethodName для выполнения метода .NET ReturnArrayAsync:

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

Примечание

Пример метода ReturnArrayAsync в этом разделе возвращает результат Task без использования явных ключевых слов C# (async и await). Методы программирования с помощью async и await являются типичными для методов, которые используют ключевое слово await для возврата значений асинхронных операций.

Метод ReturnArrayAsync, состоящий из ключевых слов async и await:

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

Дополнительные сведения см. в статье Асинхронное программирование с использованием ключевых слов async и await руководства по C#.

Вызов метода .NET экземпляра

Чтобы вызвать метод .NET экземпляра из JavaScript (JS), выполните приведенные ниже действия.

  • Передайте экземпляр .NET по ссылке JS, заключив в оболочку экземпляр в объект DotNetObjectReference и вызвав для него метод Create.
  • Вызовите метод экземпляра .NET из JS, используя invokeMethod или invokeMethodAsync из переданного метода DotNetObjectReference. Экземпляр .NET можно также передать в качестве аргумента при вызове других методов .NET из JS.
  • Удалите DotNetObjectReference.

Примеры экземпляров компонентов

Следующая функция sayHello1 JS получает DotNetObjectReference и вызывает invokeMethodAsync для вызова метода .NET GetHelloMethod компонента.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Layout.cshtml (Blazor Server):

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

Для следующего компонента CallDotNetExample2:

  • Компонент имеет вызываемый JS метод .NET с именем GetHelloMessage.
  • При нажатии кнопки Trigger .NET instance method функция JS sayHello1 вызывается с помощью метода DotNetObjectReference.
  • sayHello1:
    • Вызывает GetHelloMessage и получает результат сообщения.
    • Возвращает результат сообщения в вызывающий метод TriggerDotNetInstanceMethod.
  • Возвращенное сообщение из sayHello1 в result отображается пользователю.
  • Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

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

Примеры экземпляров класса

Следующая функция sayHello1 JS:

  • вызывает метод .NET GetHelloMessage для переданного метода DotNetObjectReference;
  • возвращает сообщение из GetHelloMessage вызывающему объекту sayHello1.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Layout.cshtml (Blazor Server):

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

Метод CallHelloHelperGetHelloMessage в следующем классе JsInteropClasses3 вызывает функцию JS sayHello1 с новым экземпляром HelloHelper.

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

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

При нажатии кнопки Trigger .NET instance method в следующем компоненте CallDotNetExample4, JsInteropClasses3.CallHelloHelperGetHelloMessage вызывается со значением name.

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

На следующем изображении показан отображаемый компонент с именем Amy Pond в поле Name. После нажатия кнопки в пользовательском интерфейсе отобразится Hello, Amy Pond!:

Пример компонента CallDotNetExample4, преобразованный для просмотра

Предыдущий шаблон, показанный в классе 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();
    }
}

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

Для выходных данных компонента CallDotNetExample5 отображается Hello, Amy Pond! в том случае, если в поле Name указано имя Amy Pond.

В предыдущем компоненте CallDotNetExample5 ссылка на объект .NET удалена. Если класс или компонент не удаляет объект DotNetObjectReference, удалите его из клиента, вызвав метод dispose в переданном объекте DotNetObjectReference:

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 метод UpdateMessage ListItemизменяет текст элемента списка и скрывает кнопку.

Следующий класс MessageUpdateInvokeHelper поддерживает вызываемый JS метод .NET (UpdateMessageCaller) для вызова объекта Action, указанного при создании экземпляра класса. BlazorSample — это имя сборки приложения.

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

Следующая функция updateMessageCaller JS вызывает метод .NET UpdateMessageCaller. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Layout.cshtml (Blazor Server):

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

Следующий компонент ListItem является общим компонентом, который можно использовать любое количество раз в родительском компоненте. Он создает элементы списка (<li>...</li>) для списка HTML (<ul>...</ul> или <ol>...</ol>). Каждый экземпляр компонента ListItem устанавливает экземпляр MessageUpdateInvokeHelper, а для Action задается метод UpdateMessage.

Если нажата кнопка InteropCall компонента ListItem, updateMessageCaller вызывается с созданным объектом DotNetObjectReference для экземпляра MessageUpdateInvokeHelper. Это позволяет платформе вызывать UpdateMessageCaller в этом экземпляре MessageUpdateInvokeHelper объекта ListItem. Переданный объект 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 вызывается для обновления пользовательского интерфейса, если параметр message имеет значение UpdateMessage. Если метод StateHasChanged не вызывается, Blazor не может узнать, что пользовательский интерфейс должен быть обновлен при вызове метода Action.

Следующий родительский компонент CallDotNetExample6 содержит четыре элемента списка, каждый из которых является экземпляром компонента ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

На следующем изображении показан отображаемый родительский компонент CallDotNetExample6 после нажатия второй кнопки InteropCall :

  • Второй компонент ListItem выводит сообщение UpdateMessage Called!.
  • Кнопка InteropCall для второго компонента ListItem не отображается, так как свойство CSS для кнопки display имеет значение none.

Пример компонента CallDotNetExample6, преобразованный для просмотра

Расположение кода JavaScipt

Загрузите код JavaScript (JS) любым из методов, описанных в обзорной статье о взаимодействии с JS:

Сведения об изоляции скриптов в модулях JS см. в разделе Изоляция JavaScript в модулях JavaScript.

Предупреждение

Не помещайте тег <script> в файл компонента (.razor), так как тег <script> не может изменяться динамически.

Исключение циклических ссылок на объекты

Объекты, содержащие циклические ссылки, не могут быть сериализованы на клиенте для:

  • вызовов метода .NET.
  • Вызов метода JavaScript из C#, если тип возвращаемого значения имеет циклические ссылки.

Поддержка массивов байтов

Blazor поддерживает оптимизированное взаимодействие с массивом байтов JS, которое позволяет избежать кодирования и декодирования массивов байтов в Base64. В следующем примере используется взаимодействие JS для передачи массива байтов в .NET.

Внутри закрывающего тега </body> для wwwroot/index.html (Blazor WebAssembly) или Pages/_Layout.cshtml (Blazor Server) укажите функцию JS sendByteArray. Функция вызывается кнопкой в компоненте и не возвращает значение:

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

Сведения об использовании массива байтов при вызове JavaScript из .NET см. в разделе Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Ограничения размера для вызовов взаимодействия с JavaScript

Этот раздел относится только к приложениям Blazor Server. В Blazor WebAssembly платформа не накладывает ограничений на размер входных и выходных данных в вызовах взаимодействия с JavaScript (JS).

В Blazor Server размер данных в вызовах взаимодействия с JS ограничен максимальным размером входящего сообщения SignalR, разрешенным для методов концентратора, который задается с помощью HubOptions.MaximumReceiveMessageSize (по умолчанию: 32 КБ). Если размер сообщений SignalR JS для .NET превышает MaximumReceiveMessageSize, возникает ошибка. Платформа не накладывает ограничение на размер сообщений SignalR от концентратора клиенту.

Если для ведения журнала SignalR не установлен уровень Отладка или Трассировка, ошибка в связи с недопустимым размером сообщения отображается только в консоли средств разработчика браузера:

Ошибка: Подключение разорвано с ошибкой "Ошибка. Сервер вернул ошибку при закрытии: соединение закрыто с ошибкой".

Если для ведения журнала на стороне сервера SignalR установлен уровень Отладка или Трассировка, функция ведения журнала на стороне сервера предоставляет InvalidDataException для ошибки в связи с недопустимым размером.

appsettings.Development.json:

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

Ошибка:

System.IO.InvalidDataException: Превышен максимальный размер сообщения 32768 Б. Размер сообщения можно настроить в AddHubOptions.

Увеличьте ограничение, настроив MaximumReceiveMessageSize в Startup.ConfigureServices. В следующем примере для размера получаемого сообщения устанавливается максимальный размер 64 КБ (64 * 1024):

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

Увеличение предельного размера для входящих сообщений SignalR потребует больше серверных ресурсов для обработки, а также повысит уязвимость сервера к злонамеренным действиям пользователей. Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к неудачным выделениям памяти, которые плохо обрабатываются сборщиком мусора, что дополнительно снизит производительность.

Чтобы считывать полезные данные большого размера, можно отправлять такое содержимое меньшими фрагментами и обрабатывать их как Stream. Это удобно для чтения полезных данных большого объема в формате JSON или необработанных байтов из JS. Вы можете изучить отправку больших двоичных данных в Blazor Server с помощью методов, эквивалентных работе компонента InputFile, в примере приложения с отправкой двоичных данных.

Примечание

По предыдущим ссылкам в документации на справочные материалы по ASP.NET Core загружается ветвь main репозитория, которая представляет текущую разработку единицы продукта для следующего выпуска ASP.NET Core. Чтобы выбрать ветвь для другого выпуска, используйте раскрывающийся список Switch branches/tags (Переключение ветвей или тегов). Например, выберите ветвь release/5.0 для выпуска ASP.NET Core 5.0.

При разработке кода, который передает большие объемы данных между JavaScript и Blazor в приложениях Blazor Server, учитывайте следующие рекомендации:

  • Разделите данные на небольшие части и отправляйте сегменты данных последовательно, пока все данные не будут получены сервером.
  • Не выделяйте большие объекты в коде C# и JS.
  • Не блокируйте основной поток пользовательского интерфейса на длительные периоды при отправке или получении данных.
  • Освободите занятую память при завершении или отмене процесса.
  • Применяйте следующие дополнительные требования в целях безопасности:
    • Объявите максимальный размер файла или данных, который может быть передан.
    • Объявите минимальную скорость передачи от клиента к серверу.
  • После получения данных сервером данные могут быть:
    • Временно сохранены в буфере памяти до тех пор, пока не будут собраны все сегменты.
    • Использованы немедленно. Например, данные могут храниться сразу в базе данных или записываться на диск по мере получения каждого сегмента.

Изоляция JavaScript в модулях JavaScript

Blazor реализует изоляцию JavaScript (JS) в стандартных модулях JavaScript (см. спецификацию ECMAScript).

Изоляция JS обеспечивает следующие преимущества:

  • Импортированный JS не засоряет глобальное пространство имен.
  • Пользователям библиотеки и компонентов не требуется импортировать связанный код JS.

Для получения дополнительной информации см. Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Дополнительные ресурсы

В этой статье рассматривается вызов методов .NET из JavaScript (JS). Сведения о том, как вызывать функции JS из .NET, см. здесь: Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Вызов статического метода .NET

Чтобы вызвать статический метод .NET из JavaScript (JS), используйте функции JS (DotNet.invokeMethod или DotNet.invokeMethodAsync). Передайте имя сборки, содержащей метод, идентификатор статического метода .NET и любые аргументы.

В следующем примере:

  • Заполнитель {ASSEMBLY NAME} — это имя сборки приложения.
  • Заполнитель {.NET METHOD ID} является идентификатором метода .NET.
  • Заполнитель {ARGUMENTS} необязателен. Аргументы, разделенные запятыми, передаются в метод, каждый из которых должен сериализоваться в JSON.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

Объект DotNet.invokeMethod возвращает результат операции. Объект DotNet.invokeMethodAsync возвращает значение типа JS Promise, представляющее результат операции.

Асинхронная функция (invokeMethodAsync) является предпочтительной по сравнению с синхронной версией (invokeMethod) для поддержки сценариев Blazor Server.

Метод .NET должен быть открытым, статическим и иметь атрибут [JSInvokable].

В следующем примере:

  • Заполнитель {<T>} указывает тип возвращаемого значения, который требуется только для методов, возвращающих значение.
  • Заполнитель {.NET METHOD ID} является идентификатором метода.
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Вызов открытых универсальных методов не поддерживается со статическими методами .NET, но поддерживается с методами экземпляров, которые описаны далее в этой статье.

В следующем компоненте CallDotNetExample1 метод C# ReturnArrayAsync возвращает массив 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 });
    }
}

HTML-атрибут onclick элемента <button> — это назначение обработчика событий JavaScript onclick для обработки событий click, а не атрибут @onclick директивы Blazor. Функция returnArrayAsync JS назначается в качестве обработчика.

Следующая функция returnArrayAsync JS вызывает метод .NET ReturnArrayAsync предыдущего компонента CallDotNetExample1 и записывает результат в консоль веб-средств разработчика браузера. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

При нажатии кнопки Trigger .NET static method в выходных данных консоли средств разработчика в браузере отображаются данные массива. Формат выходных данных незначительно различается в разных браузерах. Следующий результат имеет формат, используемый Microsoft Edge.

Array(3) [ 1, 2, 3 ]

По умолчанию идентификатором метода .NET для вызова JS является имя метода .NET, но можно указать другой идентификатор с помощью конструктора атрибута [JSInvokable]. В следующем примере DifferentMethodName — это назначенный идентификатор для метода ReturnArrayAsync:

[JSInvokable("DifferentMethodName")]

В вызове DotNet.invokeMethod или DotNet.invokeMethodAsync вызовите метод DifferentMethodName для выполнения метода .NET ReturnArrayAsync:

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

Примечание

Пример метода ReturnArrayAsync в этом разделе возвращает результат Task без использования явных ключевых слов C# (async и await). Методы программирования с помощью async и await являются типичными для методов, которые используют ключевое слово await для возврата значений асинхронных операций.

Метод ReturnArrayAsync, состоящий из ключевых слов async и await:

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

Дополнительные сведения см. в статье Асинхронное программирование с использованием ключевых слов async и await руководства по C#.

Вызов метода .NET экземпляра

Чтобы вызвать метод .NET экземпляра из JavaScript (JS), выполните приведенные ниже действия.

  • Передайте экземпляр .NET по ссылке JS, заключив в оболочку экземпляр в объект DotNetObjectReference и вызвав для него метод Create.
  • Вызовите метод экземпляра .NET из JS, используя invokeMethod или invokeMethodAsync из переданного метода DotNetObjectReference. Экземпляр .NET можно также передать в качестве аргумента при вызове других методов .NET из JS.
  • Удалите DotNetObjectReference.

Примеры экземпляров компонентов

Следующая функция sayHello1 JS получает DotNetObjectReference и вызывает invokeMethodAsync для вызова метода .NET GetHelloMethod компонента.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Для следующего компонента CallDotNetExample2:

  • Компонент имеет вызываемый JS метод .NET с именем GetHelloMessage.
  • При нажатии кнопки Trigger .NET instance method функция JS sayHello1 вызывается с помощью метода DotNetObjectReference.
  • sayHello1:
    • Вызывает GetHelloMessage и получает результат сообщения.
    • Возвращает результат сообщения в вызывающий метод TriggerDotNetInstanceMethod.
  • Возвращенное сообщение из sayHello1 в result отображается пользователю.
  • Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

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

Примеры экземпляров класса

Следующая функция sayHello1 JS:

  • вызывает метод .NET GetHelloMessage для переданного метода DotNetObjectReference;
  • возвращает сообщение из GetHelloMessage вызывающему объекту sayHello1.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Метод CallHelloHelperGetHelloMessage в следующем классе JsInteropClasses3 вызывает функцию JS sayHello1 с новым экземпляром HelloHelper.

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

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

При нажатии кнопки Trigger .NET instance method в следующем компоненте CallDotNetExample4, JsInteropClasses3.CallHelloHelperGetHelloMessage вызывается со значением name.

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

На следующем изображении показан отображаемый компонент с именем Amy Pond в поле Name. После нажатия кнопки в пользовательском интерфейсе отобразится Hello, Amy Pond!:

Пример компонента CallDotNetExample4, преобразованный для просмотра

Предыдущий шаблон, показанный в классе 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();
    }
}

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

Для выходных данных компонента CallDotNetExample5 отображается Hello, Amy Pond! в том случае, если в поле Name указано имя Amy Pond.

В предыдущем компоненте CallDotNetExample5 ссылка на объект .NET удалена. Если класс или компонент не удаляет объект DotNetObjectReference, удалите его из клиента, вызвав метод dispose в переданном объекте DotNetObjectReference:

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 метод UpdateMessage ListItemизменяет текст элемента списка и скрывает кнопку.

Следующий класс MessageUpdateInvokeHelper поддерживает вызываемый JS метод .NET (UpdateMessageCaller) для вызова объекта Action, указанного при создании экземпляра класса. BlazorSample — это имя сборки приложения.

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

Следующая функция updateMessageCaller JS вызывает метод .NET UpdateMessageCaller. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Следующий компонент ListItem является общим компонентом, который можно использовать любое количество раз в родительском компоненте. Он создает элементы списка (<li>...</li>) для списка HTML (<ul>...</ul> или <ol>...</ol>). Каждый экземпляр компонента ListItem устанавливает экземпляр MessageUpdateInvokeHelper, а для Action задается метод UpdateMessage.

Если нажата кнопка InteropCall компонента ListItem, updateMessageCaller вызывается с созданным объектом DotNetObjectReference для экземпляра MessageUpdateInvokeHelper. Это позволяет платформе вызывать UpdateMessageCaller в этом экземпляре MessageUpdateInvokeHelper объекта ListItem. Переданный объект 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 вызывается для обновления пользовательского интерфейса, если параметр message имеет значение UpdateMessage. Если метод StateHasChanged не вызывается, Blazor не может узнать, что пользовательский интерфейс должен быть обновлен при вызове метода Action.

Следующий родительский компонент CallDotNetExample6 содержит четыре элемента списка, каждый из которых является экземпляром компонента ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

На следующем изображении показан отображаемый родительский компонент CallDotNetExample6 после нажатия второй кнопки InteropCall :

  • Второй компонент ListItem выводит сообщение UpdateMessage Called!.
  • Кнопка InteropCall для второго компонента ListItem не отображается, так как свойство CSS для кнопки display имеет значение none.

Пример компонента CallDotNetExample6, преобразованный для просмотра

Расположение кода JavaScipt

Загрузите код JavaScript (JS) любым из методов, описанных в обзорной статье о взаимодействии с JS:

Сведения об изоляции скриптов в модулях JS см. в разделе Изоляция JavaScript в модулях JavaScript.

Предупреждение

Не помещайте тег <script> в файл компонента (.razor), так как тег <script> не может изменяться динамически.

Исключение циклических ссылок на объекты

Объекты, содержащие циклические ссылки, не могут быть сериализованы на клиенте для:

  • вызовов метода .NET.
  • Вызов метода JavaScript из C#, если тип возвращаемого значения имеет циклические ссылки.

Ограничения размера для вызовов взаимодействия с JavaScript

Этот раздел относится только к приложениям Blazor Server. В Blazor WebAssembly платформа не накладывает ограничений на размер входных и выходных данных в вызовах взаимодействия с JavaScript (JS).

В Blazor Server размер данных в вызовах взаимодействия с JS ограничен максимальным размером входящего сообщения SignalR, разрешенным для методов концентратора, который задается с помощью HubOptions.MaximumReceiveMessageSize (по умолчанию: 32 КБ). Если размер сообщений SignalR JS для .NET превышает MaximumReceiveMessageSize, возникает ошибка. Платформа не накладывает ограничение на размер сообщений SignalR от концентратора клиенту.

Если для ведения журнала SignalR не установлен уровень Отладка или Трассировка, ошибка в связи с недопустимым размером сообщения отображается только в консоли средств разработчика браузера:

Ошибка: Подключение разорвано с ошибкой "Ошибка. Сервер вернул ошибку при закрытии: соединение закрыто с ошибкой".

Если для ведения журнала на стороне сервера SignalR установлен уровень Отладка или Трассировка, функция ведения журнала на стороне сервера предоставляет InvalidDataException для ошибки в связи с недопустимым размером.

appsettings.Development.json:

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

Ошибка:

System.IO.InvalidDataException: Превышен максимальный размер сообщения 32768 Б. Размер сообщения можно настроить в AddHubOptions.

Увеличьте ограничение, настроив MaximumReceiveMessageSize в Startup.ConfigureServices. В следующем примере для размера получаемого сообщения устанавливается максимальный размер 64 КБ (64 * 1024):

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

Увеличение предельного размера для входящих сообщений SignalR потребует больше серверных ресурсов для обработки, а также повысит уязвимость сервера к злонамеренным действиям пользователей. Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к неудачным выделениям памяти, которые плохо обрабатываются сборщиком мусора, что дополнительно снизит производительность.

Чтобы считывать полезные данные большого размера, можно отправлять такое содержимое меньшими фрагментами и обрабатывать их как Stream. Это удобно для чтения полезных данных большого объема в формате JSON или необработанных байтов из JS. Вы можете изучить отправку больших двоичных данных в Blazor Server с помощью методов, эквивалентных работе компонента InputFile, в примере приложения с отправкой двоичных данных.

Примечание

По предыдущим ссылкам в документации на справочные материалы по ASP.NET Core загружается ветвь main репозитория, которая представляет текущую разработку единицы продукта для следующего выпуска ASP.NET Core. Чтобы выбрать ветвь для другого выпуска, используйте раскрывающийся список Switch branches/tags (Переключение ветвей или тегов). Например, выберите ветвь release/5.0 для выпуска ASP.NET Core 5.0.

При разработке кода, который передает большие объемы данных между JavaScript и Blazor в приложениях Blazor Server, учитывайте следующие рекомендации:

  • Разделите данные на небольшие части и отправляйте сегменты данных последовательно, пока все данные не будут получены сервером.
  • Не выделяйте большие объекты в коде C# и JS.
  • Не блокируйте основной поток пользовательского интерфейса на длительные периоды при отправке или получении данных.
  • Освободите занятую память при завершении или отмене процесса.
  • Применяйте следующие дополнительные требования в целях безопасности:
    • Объявите максимальный размер файла или данных, который может быть передан.
    • Объявите минимальную скорость передачи от клиента к серверу.
  • После получения данных сервером данные могут быть:
    • Временно сохранены в буфере памяти до тех пор, пока не будут собраны все сегменты.
    • Использованы немедленно. Например, данные могут храниться сразу в базе данных или записываться на диск по мере получения каждого сегмента.

Изоляция JavaScript в модулях JavaScript

Blazor реализует изоляцию JavaScript (JS) в стандартных модулях JavaScript (см. спецификацию ECMAScript).

Изоляция JS обеспечивает следующие преимущества:

  • Импортированный JS не засоряет глобальное пространство имен.
  • Пользователям библиотеки и компонентов не требуется импортировать связанный код JS.

Для получения дополнительной информации см. Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Дополнительные ресурсы

В этой статье рассматривается вызов методов .NET из JavaScript (JS). Сведения о том, как вызывать функции JS из .NET, см. здесь: Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Вызов статического метода .NET

Чтобы вызвать статический метод .NET из JavaScript (JS), используйте функции JS (DotNet.invokeMethod или DotNet.invokeMethodAsync). Передайте имя сборки, содержащей метод, идентификатор статического метода .NET и любые аргументы.

В следующем примере:

  • Заполнитель {ASSEMBLY NAME} — это имя сборки приложения.
  • Заполнитель {.NET METHOD ID} является идентификатором метода .NET.
  • Заполнитель {ARGUMENTS} необязателен. Аргументы, разделенные запятыми, передаются в метод, каждый из которых должен сериализоваться в JSON.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

Объект DotNet.invokeMethod возвращает результат операции. Объект DotNet.invokeMethodAsync возвращает значение типа JS Promise, представляющее результат операции.

Асинхронная функция (invokeMethodAsync) является предпочтительной по сравнению с синхронной версией (invokeMethod) для поддержки сценариев Blazor Server.

Метод .NET должен быть открытым, статическим и иметь атрибут [JSInvokable].

В следующем примере:

  • Заполнитель {<T>} указывает тип возвращаемого значения, который требуется только для методов, возвращающих значение.
  • Заполнитель {.NET METHOD ID} является идентификатором метода.
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Вызов открытых универсальных методов не поддерживается со статическими методами .NET, но поддерживается с методами экземпляров, которые описаны далее в этой статье.

В следующем компоненте CallDotNetExample1 метод C# ReturnArrayAsync возвращает массив 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 });
    }
}

HTML-атрибут onclick элемента <button> — это назначение обработчика событий JavaScript onclick для обработки событий click, а не атрибут @onclick директивы Blazor. Функция returnArrayAsync JS назначается в качестве обработчика.

Следующая функция returnArrayAsync JS вызывает метод .NET ReturnArrayAsync предыдущего компонента CallDotNetExample1 и записывает результат в консоль веб-средств разработчика браузера. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

При нажатии кнопки Trigger .NET static method в выходных данных консоли средств разработчика в браузере отображаются данные массива. Формат выходных данных незначительно различается в разных браузерах. Следующий результат имеет формат, используемый Microsoft Edge.

Array(3) [ 1, 2, 3 ]

По умолчанию идентификатором метода .NET для вызова JS является имя метода .NET, но можно указать другой идентификатор с помощью конструктора атрибута [JSInvokable]. В следующем примере DifferentMethodName — это назначенный идентификатор для метода ReturnArrayAsync:

[JSInvokable("DifferentMethodName")]

В вызове DotNet.invokeMethod или DotNet.invokeMethodAsync вызовите метод DifferentMethodName для выполнения метода .NET ReturnArrayAsync:

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

Примечание

Пример метода ReturnArrayAsync в этом разделе возвращает результат Task без использования явных ключевых слов C# (async и await). Методы программирования с помощью async и await являются типичными для методов, которые используют ключевое слово await для возврата значений асинхронных операций.

Метод ReturnArrayAsync, состоящий из ключевых слов async и await:

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

Дополнительные сведения см. в статье Асинхронное программирование с использованием ключевых слов async и await руководства по C#.

Вызов метода .NET экземпляра

Чтобы вызвать метод .NET экземпляра из JavaScript (JS), выполните приведенные ниже действия.

  • Передайте экземпляр .NET по ссылке JS, заключив в оболочку экземпляр в объект DotNetObjectReference и вызвав для него метод Create.
  • Вызовите метод экземпляра .NET из JS, используя invokeMethod или invokeMethodAsync из переданного метода DotNetObjectReference. Экземпляр .NET можно также передать в качестве аргумента при вызове других методов .NET из JS.
  • Удалите DotNetObjectReference.

Примеры экземпляров компонентов

Следующая функция sayHello1 JS получает DotNetObjectReference и вызывает invokeMethodAsync для вызова метода .NET GetHelloMethod компонента.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Для следующего компонента CallDotNetExample2:

  • Компонент имеет вызываемый JS метод .NET с именем GetHelloMessage.
  • При нажатии кнопки Trigger .NET instance method функция JS sayHello1 вызывается с помощью метода DotNetObjectReference.
  • sayHello1:
    • Вызывает GetHelloMessage и получает результат сообщения.
    • Возвращает результат сообщения в вызывающий метод TriggerDotNetInstanceMethod.
  • Возвращенное сообщение из sayHello1 в result отображается пользователю.
  • Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

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

Примеры экземпляров класса

Следующая функция sayHello1 JS:

  • вызывает метод .NET GetHelloMessage для переданного метода DotNetObjectReference;
  • возвращает сообщение из GetHelloMessage вызывающему объекту sayHello1.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Метод CallHelloHelperGetHelloMessage в следующем классе JsInteropClasses3 вызывает функцию JS sayHello1 с новым экземпляром HelloHelper.

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

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

При нажатии кнопки Trigger .NET instance method в следующем компоненте CallDotNetExample4, JsInteropClasses3.CallHelloHelperGetHelloMessage вызывается со значением name.

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

На следующем изображении показан отображаемый компонент с именем Amy Pond в поле Name. После нажатия кнопки в пользовательском интерфейсе отобразится Hello, Amy Pond!:

Пример компонента CallDotNetExample4, преобразованный для просмотра

Предыдущий шаблон, показанный в классе 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();
    }
}

Чтобы избежать утечки памяти и разрешить сборку мусора, ссылка на объект .NET, созданная DotNetObjectReference, будет удалена в методе Dispose.

Для выходных данных компонента CallDotNetExample5 отображается Hello, Amy Pond! в том случае, если в поле Name указано имя Amy Pond.

В предыдущем компоненте CallDotNetExample5 ссылка на объект .NET удалена. Если класс или компонент не удаляет объект DotNetObjectReference, удалите его из клиента, вызвав метод dispose в переданном объекте DotNetObjectReference:

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 метод UpdateMessage ListItemизменяет текст элемента списка и скрывает кнопку.

Следующий класс MessageUpdateInvokeHelper поддерживает вызываемый JS метод .NET (UpdateMessageCaller) для вызова объекта Action, указанного при создании экземпляра класса. BlazorSample — это имя сборки приложения.

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

Следующая функция updateMessageCaller JS вызывает метод .NET UpdateMessageCaller. BlazorSample — это имя сборки приложения.

Внутри закрывающего тега </body> объекта wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server):

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

Следующий компонент ListItem является общим компонентом, который можно использовать любое количество раз в родительском компоненте. Он создает элементы списка (<li>...</li>) для списка HTML (<ul>...</ul> или <ol>...</ol>). Каждый экземпляр компонента ListItem устанавливает экземпляр MessageUpdateInvokeHelper, а для Action задается метод UpdateMessage.

Если нажата кнопка InteropCall компонента ListItem, updateMessageCaller вызывается с созданным объектом DotNetObjectReference для экземпляра MessageUpdateInvokeHelper. Это позволяет платформе вызывать UpdateMessageCaller в этом экземпляре MessageUpdateInvokeHelper объекта ListItem. Переданный объект 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 вызывается для обновления пользовательского интерфейса, если параметр message имеет значение UpdateMessage. Если метод StateHasChanged не вызывается, Blazor не может узнать, что пользовательский интерфейс должен быть обновлен при вызове метода Action.

Следующий родительский компонент CallDotNetExample6 содержит четыре элемента списка, каждый из которых является экземпляром компонента ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

На следующем изображении показан отображаемый родительский компонент CallDotNetExample6 после нажатия второй кнопки InteropCall :

  • Второй компонент ListItem выводит сообщение UpdateMessage Called!.
  • Кнопка InteropCall для второго компонента ListItem не отображается, так как свойство CSS для кнопки display имеет значение none.

Пример компонента CallDotNetExample6, преобразованный для просмотра

Расположение кода JavaScipt

Загрузите код JavaScript (JS) любым из методов, описанных в обзорной статье о взаимодействии с JS:

Предупреждение

Не помещайте тег <script> в файл компонента (.razor), так как тег <script> не может изменяться динамически.

Исключение циклических ссылок на объекты

Объекты, содержащие циклические ссылки, не могут быть сериализованы на клиенте для:

  • вызовов метода .NET.
  • Вызов метода JavaScript из C#, если тип возвращаемого значения имеет циклические ссылки.

Ограничения размера для вызовов взаимодействия с JavaScript

Этот раздел относится только к приложениям Blazor Server. В Blazor WebAssembly платформа не накладывает ограничений на размер входных и выходных данных в вызовах взаимодействия с JavaScript (JS).

В Blazor Server размер данных в вызовах взаимодействия с JS ограничен максимальным размером входящего сообщения SignalR, разрешенным для методов концентратора, который задается с помощью HubOptions.MaximumReceiveMessageSize (по умолчанию: 32 КБ). Если размер сообщений SignalR JS для .NET превышает MaximumReceiveMessageSize, возникает ошибка. Платформа не накладывает ограничение на размер сообщений SignalR от концентратора клиенту.

Если для ведения журнала SignalR не установлен уровень Отладка или Трассировка, ошибка в связи с недопустимым размером сообщения отображается только в консоли средств разработчика браузера:

Ошибка: Подключение разорвано с ошибкой "Ошибка. Сервер вернул ошибку при закрытии: соединение закрыто с ошибкой".

Если для ведения журнала на стороне сервера SignalR установлен уровень Отладка или Трассировка, функция ведения журнала на стороне сервера предоставляет InvalidDataException для ошибки в связи с недопустимым размером.

appsettings.Development.json:

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

Ошибка:

System.IO.InvalidDataException: Превышен максимальный размер сообщения 32768 Б. Размер сообщения можно настроить в AddHubOptions.

Увеличьте ограничение, настроив MaximumReceiveMessageSize в Startup.ConfigureServices. В следующем примере для размера получаемого сообщения устанавливается максимальный размер 64 КБ (64 * 1024):

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

Увеличение предельного размера для входящих сообщений SignalR потребует больше серверных ресурсов для обработки, а также повысит уязвимость сервера к злонамеренным действиям пользователей. Кроме того, считывание большого объема содержимого в память в виде строк или массивов байтов может привести к неудачным выделениям памяти, которые плохо обрабатываются сборщиком мусора, что дополнительно снизит производительность.

Чтобы считывать полезные данные большого размера, можно отправлять такое содержимое меньшими фрагментами и обрабатывать их как Stream. Это удобно для чтения полезных данных большого объема в формате JSON или необработанных байтов из JS. Вы можете изучить отправку больших двоичных данных в Blazor Server с помощью методов, эквивалентных работе компонента InputFile, в примере приложения с отправкой двоичных данных.

Примечание

По предыдущим ссылкам в документации на справочные материалы по ASP.NET Core загружается ветвь main репозитория, которая представляет текущую разработку единицы продукта для следующего выпуска ASP.NET Core. Чтобы выбрать ветвь для другого выпуска, используйте раскрывающийся список Switch branches/tags (Переключение ветвей или тегов). Например, выберите ветвь release/5.0 для выпуска ASP.NET Core 5.0.

При разработке кода, который передает большие объемы данных между JavaScript и Blazor в приложениях Blazor Server, учитывайте следующие рекомендации:

  • Разделите данные на небольшие части и отправляйте сегменты данных последовательно, пока все данные не будут получены сервером.
  • Не выделяйте большие объекты в коде C# и JS.
  • Не блокируйте основной поток пользовательского интерфейса на длительные периоды при отправке или получении данных.
  • Освободите занятую память при завершении или отмене процесса.
  • Применяйте следующие дополнительные требования в целях безопасности:
    • Объявите максимальный размер файла или данных, который может быть передан.
    • Объявите минимальную скорость передачи от клиента к серверу.
  • После получения данных сервером данные могут быть:
    • Временно сохранены в буфере памяти до тех пор, пока не будут собраны все сегменты.
    • Использованы немедленно. Например, данные могут храниться сразу в базе данных или записываться на диск по мере получения каждого сегмента.

Дополнительные ресурсы