Поделиться через


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

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

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

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 9 этой статьи.

В этой статье объясняется, как вызывать методы .NET в JavaScript (JS).

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

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

Чтобы вызвать статический метод .NET в JavaScript (JS), воспользуйтесь функциями JS:

  • DotNet.invokeMethodAsync (рекомендуется): асинхронный для компонентов на стороне сервера и на стороне клиента.
  • DotNet.invokeMethod: синхронный только для клиентских компонентов.

Передайте имя сборки, содержащей метод, идентификатор статического метода .NET и любые аргументы.

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

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

DotNet.invokeMethodAsyncJS Promise возвращает результат операции. DotNet.invokeMethod (клиентские компоненты) возвращает результат операции.

Внимание

Для компонентов на стороне сервера рекомендуется асинхронная функция (invokeMethodAsync) для синхронной версии (invokeMethod).

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

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

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

Примечание.

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

В следующем компоненте ReturnArrayAsync метод C# возвращает int массив. Атрибут [JSInvokable] применяется к методу, который делает метод вызываемым с помощью JS.

CallDotnet1.razor:

@page "/call-dotnet-1"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 1</PageTitle>

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

<p>
    <button id="btn">Trigger .NET static method</button>
</p>

<p>
    See the result in the developer tools console.
</p>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/CallDotnet1.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync() =>
        Task.FromResult(new int[] { 11, 12, 13 });

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

CallDotnet1.razor.js:

export function returnArrayAsync() {
  DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
    .then(data => {
      console.log(data);
    });
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", returnArrayAsync);
}

Функция addHandlersJS добавляет click событие в кнопку. Функция returnArrayAsync JS назначается в качестве обработчика.

Функция returnArrayAsyncJS вызывает ReturnArrayAsync метод .NET компонента, который записывает результат в консоль средств веб-разработчика браузера. BlazorSample — это имя сборки приложения.

CallDotnet1.razor:

@page "/call-dotnet-1"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 1</PageTitle>

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

<p>
    <button id="btn">Trigger .NET static method</button>
</p>

<p>
    See the result in the developer tools console.
</p>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/CallDotnet1.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync() =>
        Task.FromResult(new int[] { 11, 12, 13 });

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

CallDotnet1.razor.js:

export function returnArrayAsync() {
  DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
    .then(data => {
      console.log(data);
    });
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", returnArrayAsync);
}

Функция addHandlersJS добавляет click событие в кнопку. Функция returnArrayAsync JS назначается в качестве обработчика.

Функция returnArrayAsyncJS вызывает ReturnArrayAsync метод .NET компонента, который записывает результат в консоль средств веб-разработчика браузера. BlazorSample — это имя сборки приложения.

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

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

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

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 назначается в качестве обработчика.

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

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

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

Array(3) [ 11, 12, 13 ]

Передайте данные в метод .NET при вызове invokeMethodAsync функции путем передачи данных в качестве аргументов.

Чтобы продемонстрировать передачу данных в .NET, передайте начальную позицию ReturnArrayAsync в метод, в котором вызывается метод:JS

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

Вызываемый ReturnArrayAsync метод компонента получает начальную позицию и создает из него массив. Массив возвращается для ведения журнала в консоли:

[JSInvokable]
public static Task<int[]> ReturnArrayAsync(int startPosition) => 
    Task.FromResult(Enumerable.Range(startPosition, 3).ToArray());

После повторной компиляции приложения и обновления браузера при выборе кнопки в консоли браузера отображаются следующие выходные данные:

Array(3) [ 14, 15, 16 ]

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

[JSInvokable("DifferentMethodName")]

В вызове DotNet.invokeMethodAsync (серверные или клиентские компоненты) или DotNet.invokeMethod (только на стороне клиента) вызов метода DifferentMethodName ReturnArrayAsync .NET:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (только клиентские компоненты)

Примечание.

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

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

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync() => 
    await Task.FromResult(new int[] { 11, 12, 13 });

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

Создание объектов JavaScript и ссылок на данные для передачи в .NET

Вызовите DotNet.createJSObjectReference(jsObject) ссылку на JS объект, чтобы она была передана в .NET, где jsObject используется JS Object для создания ссылки на JS объект. В следующем примере передается ссылка на несериализируемый window объект в .NET, который получает его в методе ReceiveWindowObject C# в виде IJSObjectReference:

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

В предыдущем примере {ASSEMBLY NAME} заполнитель — это пространство имен приложения.

Примечание.

В предыдущем примере не требуется удаление JSObjectReferenceобъекта, так как ссылка на window объект не содержится JS.

Для поддержания ссылки на ссылку на нее JSObjectReference требуется удалить ее, чтобы избежать утечки JS памяти на клиенте. В следующем примере выполняется рефакторинг предыдущего кода для записи ссылки на нее JSObjectReference, а затем вызов DotNet.disposeJSObjectReference() для удаления ссылки:

var jsObjectReference = DotNet.createJSObjectReference(window);

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', 'ReceiveWindowObject', jsObjectReference);

DotNet.disposeJSObjectReference(jsObjectReference);

В предыдущем примере {ASSEMBLY NAME} заполнитель — это пространство имен приложения.

Вызов для DotNet.createJSStreamReference(streamReference) создания ссылки на поток таким образом, чтобы он был передан в .NET, где streamReference находится ArrayBufferBlobили любой типизированный массив, например Uint8Array или Float32Arrayиспользуемый для создания ссылки на JS JS поток.

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

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

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

  • Вызов метода экземпляра .NET из JS invokeMethodAsync переданного DotNetObjectReferenceкомпонента (рекомендуется) или invokeMethod (только на стороне клиента). Передайте идентификатор метода .NET экземпляра и все аргументы. Экземпляр .NET можно также передать в качестве аргумента при вызове других методов .NET из JS.

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

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

    Примечание.

    invokeMethodAsync и invokeMethod не принимаю параметр имени сборки при вызове метода экземпляра.

    invokeMethodAsyncJS Promise возвращает результат операции. invokeMethod (только клиентские компоненты) возвращает результат операции.

    Внимание

    Для компонентов на стороне сервера рекомендуется асинхронная функция (invokeMethodAsync) для синхронной версии (invokeMethod).

  • Удалите DotNetObjectReference.

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

Избегайте обрезки вызываемых методов JavaScript .NET

Этот раздел относится к клиентским приложениям с включенной компиляцией (AOT) и повторной связью среды выполнения.

Несколько примеров в следующих разделах основаны на подходе к экземпляру класса, где метод JavaScript, вызывающий .NET, [JSInvokable] помеченный атрибутом , является членом класса, который не является компонентом Razor . Если такие методы .NET находятся в компоненте, они защищены от повторной Razor связи и обрезки среды выполнения. Чтобы защитить методы .NET от обрезки вне Razor компонентов, реализуйте методы с DynamicDependency атрибутом в конструкторе класса, как показано в следующем примере:

using System.Diagnostics.CodeAnalysis;
using Microsoft.JSInterop;

public class ExampleClass {

    [DynamicDependency(nameof(ExampleJSInvokableMethod))]
    public ExampleClass()
    {
    }

    [JSInvokable]
    public string ExampleJSInvokableMethod()
    {
        ...
    }
}

Дополнительные сведения см. в статье "Подготовка библиотек .NET для обрезки: DynamicDependency".

Передайте DotNetObjectReference в отдельную функцию JavaScript

В примере этого раздела описывается, как передать DotNetObjectReference в отдельную функцию JavaScript (JS).

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

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

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

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

CallDotnet2.razor:

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

<PageTitle>Call .NET 2</PageTitle>

<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<CallDotnet2>? objRef;

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

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

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

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

CallDotnet2.razor:

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

<PageTitle>Call .NET 2</PageTitle>

<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<CallDotnet2>? objRef;

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

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

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

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

CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

Используйте следующее руководство для передачи аргументов методу экземпляра.

Добавьте параметры в вызов метода .NET. В приведенном ниже примере в метод передается имя. При необходимости добавьте дополнительные параметры в список.

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

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

Укажите список параметров для методов .NET.

CallDotnet3.razor:

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

<PageTitle>Call .NET 3</PageTitle>

<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<CallDotnet3>? objRef;

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

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

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

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

CallDotnet3.razor:

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

<PageTitle>Call .NET 3</PageTitle>

<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<CallDotnet3>? objRef;

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

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

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

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

CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

Передайте DotNetObjectReference в класс с несколькими функциями JavaScript

В примере этого раздела описывается, как передать DotNetObjectReference в класс JavaScript (JS) с несколькими функциями.

Создайте и передайте DotNetObjectReference из метода жизненного цикла OnAfterRenderAsync в класс JS, чтобы его могли использовать несколько функций. Убедитесь, что код .NET удаляет DotNetObjectReference, как показано в следующем примере.

В следующем компоненте кнопки вызывают функции, Trigger JS function задав JSonclick свойство, а не Blazor@onclick атрибут директивы.JS

CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IAsyncDisposable
@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 id="sayHelloBtn">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button id="welcomeVisitorBtn">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

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

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/CallDotNetExampleOneHelper.razor.js");

            dotNetHelper = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);

            await module.InvokeVoidAsync("addHandlers");
        }
    }

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

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

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }

        dotNetHelper?.Dispose();
    }
}

В предыдущем примере:

  • JS является внедренным экземпляром IJSRuntime. IJSRuntime регистрируется платформой Blazor.
  • Имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.
  • Компонент должен явным образом удалить DotNetObjectReference, чтобы разрешить сбор мусора и предотвратить утечку памяти.
  • JSDisconnectedExceptionнаходится в ловушке во время удаления модуля в случае BlazorSignalR потери канала. Если предыдущий код используется в Blazor WebAssembly приложении, нет подключения к потере, SignalR поэтому можно удалить блок и оставить строку, которая удаляет try-catch модуль (await module.DisposeAsync();). Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

CallDotNetExampleOneHelper.razor.js:

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

export function addHandlers() {
  const sayHelloBtn = document.getElementById("sayHelloBtn");
  sayHelloBtn.addEventListener("click", GreetingHelpers.sayHello);

  const welcomeVisitorBtn = document.getElementById("welcomeVisitorBtn");
  welcomeVisitorBtn.addEventListener("click", GreetingHelpers.welcomeVisitor);
}

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

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

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

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

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

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

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

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

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

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

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

В предыдущем примере:

  • JS является внедренным экземпляром IJSRuntime. IJSRuntime регистрируется платформой Blazor.
  • Имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.
  • Компонент должен явным образом удалить DotNetObjectReference, чтобы разрешить сбор мусора и предотвратить утечку памяти.
<script>
  class GreetingHelpers {
    static dotNetHelper;

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

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

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

  window.GreetingHelpers = GreetingHelpers;
</script>

В предыдущем примере:

  • Класс GreetingHelpers добавляется в объект window, чтобы глобально определить класс, разрешающий Blazor находить класс для взаимодействия JS.
  • Имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Вызов универсальных методов классов .NET

Функции JavaScript (JS) могут вызывать методы универсальных классов .NET, в которых функция JS вызывает метод .NET универсального класса.

В следующем классе универсального типа (GenericType<TValue>):

  • Класс содержит один параметр типа (TValue) с одним универсальным свойством Value.
  • Класс имеет два не универсальных метода, [JSInvokable] помеченных атрибутом, каждый из которых имеет параметр newValueуниверсального типа:
    • Update синхронно обновляет значение Value из newValue.
    • UpdateAsync асинхронно обновляет значение Value из newValue после создания задачи с поддержкой ожидания с методом Task.Yield, который асинхронно передает значение в текущий контекст, находясь в ожидании.
  • Каждый из методов класса записывает тип TValue и значение Value в консоль. Запись в консоль необходима исключительно для демонстрации. Рабочие приложения обычно пропускают шаг записи в консоль, так как используют ведение журнала. Дополнительные сведения см. в статьях Ведение журнала ASP.NET Core Blazor и Ведение журнала в .NET Core и ASP.NET Core.

Примечание.

Открытые универсальные типы и методы не указывают типы для заполнителей. Напротив, закрытые универсальные шаблоны предоставляют типы для всех заполнителей. В примерах этого раздела описываются закрытые универсальные шаблоны, но при этом поддерживается и вызов методов экземпляра взаимодействия с JS с открытыми шаблонами. Использование открытых универсальных шаблонов не поддерживается для вызовов статических методов .NET, описанных ранее в этой статье.

Дополнительные сведения см. в следующих статьях:

GenericType.cs:

using Microsoft.JSInterop;

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

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

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

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

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

В следующей функции invokeMethodsAsync:

  • Методы Update и UpdateAsync класса универсального типа вызываются с аргументами, представляющими строки и числа.
  • Клиентские компоненты поддерживают синхронную invokeMethodсинхронизацию методов .NET. syncInterop получает логическое значение, указывающее, происходит ли JS взаимодействие на клиенте. Если syncInterop принимает значение true, безопасно вызывается invokeMethod. Если значение syncInterop равно false, вызывается только асинхронная функция invokeMethodAsync , так как JS взаимодействие выполняется в серверном компоненте.
  • Для демонстрации вызов функции DotNetObjectReference (invokeMethod или invokeMethodAsync), метод .NET (Update или UpdateAsync) и аргумент записываются в консоль. Аргументы используют случайное число, чтобы сопоставить вызов функции JS с вызовом метода .NET (который также записывается в консоль со стороны .NET). Рабочий код обычно не записывается в консоль ни со стороны клиента, ни со стороны сервера. Рабочие приложения используют ведение журнала. Дополнительные сведения см. в статьях Ведение журнала ASP.NET Core Blazor и Ведение журнала в .NET Core и ASP.NET Core.
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

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

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

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

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

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

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Следующий компонент GenericsExample:

  • Функция JS invokeMethodsAsync вызывается при нажатии кнопки Invoke Interop.
  • Создается пара типов DotNetObjectReference, а затем передается в функцию JS для экземпляров GenericType в качестве string и int.

GenericsExample.razor:

@page "/generics-example"
@implements IDisposable
@inject IJSRuntime JS

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

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

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

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

    public async Task InvokeInterop()
    {
        var syncInterop = OperatingSystem.IsBrowser();

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

    public void Dispose()
    {
        objRef1?.Dispose();
        objRef2?.Dispose();
    }
}
@page "/generics-example"
@implements IDisposable
@inject IJSRuntime JS

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

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

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

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

    public async Task InvokeInterop()
    {
        var syncInterop = OperatingSystem.IsBrowser();

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

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

В предыдущем примере JS — это внедренный экземпляр IJSRuntime. IJSRuntime регистрируется платформой Blazor.

Ниже показаны типичные выходные данные предыдущего примера при 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

Если приведенный выше пример реализован в серверном компоненте, синхронные вызовы invokeMethod с которыми не выполняются. Для компонентов на стороне сервера рекомендуется асинхронная функция (invokeMethodAsync) для синхронной версии (invokeMethod).

Типичные выходные данные компонента на стороне сервера:

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

В предыдущем примере выходных данных показано, что асинхронные методы выполняются в произвольном порядке в зависимости от ряда факторов, включая расписание потоков и скорость выполнения методов. Надежно спрогнозировать порядок выполнения асинхронных вызовов методов невозможно.

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

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

  • вызывает метод .NET GetHelloMessage для переданного метода DotNetObjectReference;
  • возвращает сообщение из GetHelloMessage вызывающему объекту sayHello1.
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

Следующий класс HelloHelper имеет вызываемый JS метод .NET с именем GetHelloMessage. При создании HelloHelper имя в свойстве Name используется для возврата сообщения из GetHelloMessage.

HelloHelper.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

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

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

namespace BlazorSample;

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

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
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}!";
}
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}!";
}
using Microsoft.JSInterop;

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

    public string Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
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 Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses3(IJSRuntime js)
{
    private readonly IJSRuntime js = js;

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

namespace BlazorSample;

public class JsInteropClasses3(IJSRuntime js)
{
    private readonly IJSRuntime js = js;

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

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

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

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

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

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

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

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

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

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

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

CallDotnet4.razor:

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

<PageTitle>Call .NET 4</PageTitle>

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

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

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

<p>
    @result
</p>

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

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

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

CallDotnet4.razor:

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

<PageTitle>Call .NET 4</PageTitle>

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

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

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

<p>
    @result
</p>

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

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

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

CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

Предыдущий шаблон, показанный в классе JsInteropClasses3, также можно полностью реализовать в компоненте.

CallDotnet5.razor:

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

<PageTitle>Call .NET 5</PageTitle>

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

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

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

<p>
    @result
</p>

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

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

CallDotnet5.razor:

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

<PageTitle>Call .NET 5</PageTitle>

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

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

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

<p>
    @result
</p>

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

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

CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

@code {
    private string name;
    private string result;

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

CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

@code {
    private string name;
    private string result;

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

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

Выходные данные, отображаемые компонентом, отображаются Hello, Amy Pond! при указании имени Amy Pond в name поле.

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

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

В предыдущем примере:

  • Заполнитель {JS FUNCTION NAME} — это JS имя функции.
  • Имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.
  • Заполнитель {.NET METHOD ID} является идентификатором метода .NET.

Класс вспомогательного приложения для метода .NET экземпляра компонента

Класс вспомогательного приложения может вызывать метод экземпляра .NET в качестве Action. Вспомогательные классы полезны в сценариях, когда использование статических методов .NET неприменимо:

  • когда на одной странице отображается несколько компонентов одного типа;
  • В серверных приложениях с несколькими пользователями одновременно используется один и тот же компонент.

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

  • Компонент содержит несколько ListItem1 компонентов.
  • Каждый компонент ListItem1 состоит из сообщения и кнопки.
  • При выборе кнопки компонента ListItem1 метод UpdateMessage ListItem1изменяет текст элемента списка и скрывает кнопку.

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

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class MessageUpdateInvokeHelper(Action action)
{
    private readonly Action action = action;

    [JSInvokable]
    public void UpdateMessageCaller() => action.Invoke();
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class MessageUpdateInvokeHelper(Action action)
{
    private readonly Action action = action;

    [JSInvokable]
    public void UpdateMessageCaller() => action.Invoke();
}
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

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

Следующая функция updateMessageCaller JS вызывает метод .NET UpdateMessageCaller.

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

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

Если нажата кнопка InteropCall компонента ListItem1, updateMessageCaller вызывается с созданным объектом DotNetObjectReference для экземпляра MessageUpdateInvokeHelper. Это позволяет платформе вызывать UpdateMessageCaller в этом экземпляре MessageUpdateInvokeHelper объекта ListItem1. Переданный объект DotNetObjectReference удаляется в JS (dotNetHelper.dispose()).

ListItem1.razor:

@inject IJSRuntime JS

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

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

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

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

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@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();
    }
}
@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();
    }
}
@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();
    }
}
@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();
    }
}
@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.

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

CallDotnet6.razor:

@page "/call-dotnet-6"

<PageTitle>Call .NET 6</PageTitle>

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

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

CallDotnet6.razor:

@page "/call-dotnet-6"

<PageTitle>Call .NET 6</PageTitle>

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

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

CallDotNetExample6.razor:

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

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

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

CallDotNetExample6.razor:

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

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

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

CallDotNetExample6.razor:

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

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

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

CallDotNetExample6.razor:

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

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

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

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

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

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

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

Назначение DotNetObjectReference свойству HTML-элемента позволяет вызывать методы .NET в экземпляре компонента:

Аналогично подходу, описанному в разделе вспомогательного класса метода .NET экземпляра компонента, этот подход полезен в сценариях, в которых использование статических методов .NET неприменимо:

  • когда на одной странице отображается несколько компонентов одного типа;
  • В серверных приложениях с несколькими пользователями одновременно используется один и тот же компонент.
  • Метод .NET вызывается из события JS (например, onclick), а не из события Blazor (например, @onclick).

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

  • Компонент содержит несколько ListItem2 компонентов, которые являются общим компонентом.
  • Каждый компонент ListItem2 состоит из сообщения элемента списка <span> и второго <span> со свойством CSS display, для которого задано значение inline-block для отображения.
  • Когда элемент списка компонентов ListItem2 выбран, этот метод ListItem2 UpdateMessage изменяет текст элемента списка в первом <span> и скрывает второй <span>, задавая для его свойства display значение none.

Следующая assignDotNetHelperJS функция назначает DotNetObjectReference элементу в свойстве с именем dotNetHelper. Следующая interopCallJS функция использует DotNetObjectReference переданный элемент для вызова метода .NET с именем UpdateMessage.

ListItem2.razor.js:

export function assignDotNetHelper(element, dotNetHelper) {
  element.dotNetHelper = dotNetHelper;
}

export async function interopCall(element) {
  await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}

ListItem2.razor.js:

export function assignDotNetHelper(element, dotNetHelper) {
  element.dotNetHelper = dotNetHelper;
}

export async function interopCall(element) {
  await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}
<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

В предыдущем примере имя переменной dotNetHelper является произвольным и может быть изменено на любое желаемое имя.

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

Каждый экземпляр компонента ListItem2 вызывает функцию assignDotNetHelperJS в OnAfterRenderAsync со ссылкой на элемент (первый элемент <span> в элементе списка) и экземпляром компонента как DotNetObjectReference.

Когда выбрано сообщение компонента ListItem2 <span>, вызывается interopCall для передачи элемента <span> в качестве параметра (this), который вызывает метод .NET UpdateMessage. В UpdateMessage StateHasChanged вызывается для обновления пользовательского интерфейса, когда задается message и свойство display второго <span> обновляется. Если StateHasChanged не вызывается, Blazor не может узнать, что пользовательский интерфейс должен быть обновлен при вызове метода.

DotNetObjectReference удаляется при удалении компонента.

ListItem2.razor:

@inject IJSRuntime JS
@implements IAsyncDisposable

<li>
    <span style="font-weight:bold;color:@color" @ref="elementRef"
        @onclick="CallJSToInvokeDotnet">
        @message
    </span>
    <span style="display:@display">
        Not Updated Yet!
    </span>
</li>

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

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/ListItem2.razor.js");

            objRef = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    public async void CallJSToInvokeDotnet()
    {
        if (module is not null)
        {
            await module.InvokeVoidAsync("interopCall", elementRef);
        }
    }

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

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }

        objRef?.Dispose();
    }
}
@inject IJSRuntime JS
@implements IAsyncDisposable

<li>
    <span style="font-weight:bold;color:@color" @ref="elementRef"
        @onclick="CallJSToInvokeDotnet">
        @message
    </span>
    <span style="display:@display">
        Not Updated Yet!
    </span>
</li>

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

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/ListItem2.razor.js");

            objRef = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    public async void CallJSToInvokeDotnet()
    {
        if (module is not null)
        {
            await module.InvokeVoidAsync("interopCall", elementRef);
        }
    }

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

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }

        objRef?.Dispose();
    }
}
@inject IJSRuntime JS

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

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

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

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

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

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

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

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

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

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

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

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

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

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

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

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

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

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

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

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

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

CallDotnet7.razor:

@page "/call-dotnet-7"

<PageTitle>Call .NET 7</PageTitle>

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

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

CallDotnet7.razor:

@page "/call-dotnet-7"

<PageTitle>Call .NET 7</PageTitle>

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

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

CallDotNetExample7.razor:

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

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

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

CallDotNetExample7.razor:

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

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

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

CallDotNetExample7.razor:

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

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

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

CallDotNetExample7.razor:

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

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

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

Синхронная JS взаимодействие в клиентских компонентах

Этот раздел применяется только к клиентским компонентам.

JS Вызовы взаимодействия являются асинхронными, независимо от того, синхронный или асинхронный код. Вызовы являются асинхронными, чтобы обеспечить совместимость компонентов в режимах отрисовки на стороне сервера и на стороне клиента. На сервере все JS вызовы взаимодействия должны быть асинхронными, так как они отправляются по сетевому подключению.

Если вы знаете, что компонент выполняется только в WebAssembly, можно сделать синхронные JS вызовы взаимодействия. Они создают чуть меньше нагрузки, чем асинхронные вызовы, и снижают число циклов отрисовки благодаря отсутствию промежуточного состояния на время ожидания результатов.

Чтобы сделать синхронный вызов из JavaScript в .NET в клиентском компоненте, используйте DotNet.invokeMethod вместо DotNet.invokeMethodAsyncнего.

Синхронные вызовы возможны, если соблюдаются следующие условия:

  • Компонент отображается только для выполнения в WebAssembly.
  • Вызываемая функция возвращает значение синхронно. Эта функция не является методом async и не возвращает объект Task .NET или Promise JavaScript.

Расположение JavaScript

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

Использование JS модулей для загрузки JS описано в этой статье в разделе изоляции JavaScript в модулях JavaScript.

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

Только поместите <script> тег в файл компонента (.razor), если компонент гарантированно принимает статическую отрисовку на стороне сервера (статический SSR), так как <script> тег не может быть динамически обновлен.

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

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

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

Blazor реализует изоляцию JavaScript (JS) в стандартных модулях JavaScript (см. спецификацию ECMAScript). Загрузка модуля JavaScript работает так же Blazor , как и для других типов веб-приложений, и вы можете настроить способ определения модулей в приложении. Руководство по использованию модулей JavaScript см . в веб-документации MDN: модули JavaScript.

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

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

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

Динамический import() импорт с оператором поддерживается с ASP.NET Core и Blazor:

if ({CONDITION}) import("/additionalModule.js");

В предыдущем примере заполнитель представляет условную проверку, чтобы определить, {CONDITION} следует ли загрузить модуль.

Сведения о совместимости браузера см. в разделе "Можно ли использовать: модули JavaScript: динамический импорт".

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

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

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

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

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

Укажите функцию JS sendByteArray. Функция вызывается статически, которая включает параметр имени сборки в invokeMethodAsync вызове, кнопкой в компоненте и не возвращает значение:

CallDotnet8.razor.js:

export function 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, 0x61, 0x69, 0x6e, 0x2e, 0x20, 0x4e,
    0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e]);
  DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
    .then(str => {
      alert(str);
    });
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", 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,0x61,0x69,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

CallDotnet8.razor:

@page "/call-dotnet-8"
@using System.Text
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 8</PageTitle>

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

<p>
    <button id="btn">Send Bytes</button>
</p>

<p>
    Quote ©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 {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./Components/Pages/CallDotnet8.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes) => 
        Task.FromResult(Encoding.UTF8.GetString(receivedBytes, 0, 
            receivedBytes.Length));

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

CallDotnet8.razor:

@page "/call-dotnet-8"
@using System.Text
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 8</PageTitle>

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

<p>
    <button id="btn">Send Bytes</button>
</p>

<p>
    Quote ©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 {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./Components/Pages/CallDotnet8.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes) => 
        Task.FromResult(Encoding.UTF8.GetString(receivedBytes, 0, 
            receivedBytes.Length));

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

CallDotNetExample8.razor:

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

<PageTitle>Call .NET 8</PageTitle>

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

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

<p>
    Quote ©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));
    }
}

CallDotNetExample8.razor:

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

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

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

<p>
    Quote ©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));
    }
}

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

Потоковая передача из JavaScript в .NET

Blazor поддерживает потоковую передачу данных непосредственно из JavaScript в .NET. Потоки запрашиваются с помощью интерфейса Microsoft.JSInterop.IJSStreamReference.

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync возвращает Stream со следующими параметрами:

  • maxAllowedSize: максимальное число байтов, разрешенное для операции чтения из JavaScript, которое по умолчанию равно 512 000 байт, если не указано.
  • cancellationToken: CancellationToken для отмены чтения.

В JavaScript:

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

В коде C#:

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

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

В предыдущем примере:

  • JS является внедренным экземпляром IJSRuntime. IJSRuntime регистрируется платформой Blazor.
  • dataReferenceStream записывается на диск (file.txt) по пути временной папки текущего пользователя (GetTempPath).

В статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor рассматривается обратная операция — потоковая передача данных из .NET в JavaScript с помощью DotNetStreamReference.

В статье Отправка файлов ASP.NET Core Blazor описано, как отправить файл в Blazor. Пример форм, который передает <textarea> данные в серверный компонент, см. в разделе "Устранение неполадок ASP.NET Основных Blazor форм".

Взаимодействие JavaScript [JSImport]/[JSExport]

Этот раздел относится к клиентским компонентам.

В качестве альтернативы взаимодействию с JavaScript (JS) в клиентских компонентах с помощью BlazorJS механизма взаимодействия на IJSRuntime основе интерфейса/JS[JSImport][JSExport] API взаимодействия доступен для приложений, предназначенных для .NET 7 или более поздней версии.

Дополнительные сведения см. в статье JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.

Удаление ссылок на объекты взаимодействия JavaScript

Примеры в статьях взаимодействия JavaScript (JS) демонстрируют типичные шаблоны удаления объектов:

  • При вызове .NET из JS,как описано в этой статье, удалите созданный DotNetObjectReference из .NET или из JS нее утечку памяти .NET.

  • При вызове из .NET, как описано в описании вызовов JS функций JavaScript из методов .NET в ASP.NET CoreBlazor, удалите все созданныеIJSInProcessObjectReferenceIJSObjectReference//JSObjectReference из .NET или из JS этого, чтобы избежать утечки JS памяти.

JS Ссылки на объекты взаимодействия реализуются в виде карты с ключом идентификатора на стороне JS вызова взаимодействия, создающего ссылку. Если удаление объектов инициируется из .NET или JS на стороне, удаляет запись из карты, Blazor а объект может быть собран мусором до тех пор, пока нет другой строгой ссылки на объект.

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

Задачи очистки DOM во время удаления компонентов

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Вызовы взаимодействия JavaScript без канала

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

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