Compartilhar via


Chamar métodos .NET de funções JavaScript no ASP.NET Core Blazor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como invocar os métodos do .NET a partir do JavaScript (JS).

Para obter informações sobre como chamar as funções JS a partir do .NET, confira Chamar funções JavaScript a partir dos métodos do .NET no ASP.NET Core Blazor.

Invocar um método estático do .NET

Para invocar um método estático do .NET a partir do JavaScript (JS), use as funções JS:

  • DotNet.invokeMethodAsync (recomendado): assíncrono para componentes do lado do servidor e do cliente.
  • DotNet.invokeMethod: síncrono apenas para componentes do lado do cliente.

Passe o nome do assembly que contém o método, o identificador do método estático do .NET e quaisquer argumentos.

No exemplo a seguir:

  • O espaço reservado {ASSEMBLY NAME} é o nome do assembly do aplicativo.
  • O espaço reservado {.NET METHOD ID} é o identificador do método do .NET.
  • O espaço reservado {ARGUMENTS} representa os argumentos opcionais separados por vírgulas a serem passados para o método, dentre os quais, cada um deve ser serializável em JSON.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync retorna um JS Promise que representa o resultado da operação. DotNet.invokeMethod (componentes do lado do cliente) retorna o resultado da operação.

Importante

Para componentes do lado do servidor, recomendamos a função assíncrona (invokeMethodAsync) em vez da versão síncrona (invokeMethod).

O método do .NET deve ser público e estático e deve ter o atributo [JSInvokable] .

No exemplo a seguir:

  • O espaço reservado {<T>} indica o tipo de retorno, que só é necessário para métodos que retornam um valor.
  • O espaço reservado {.NET METHOD ID} é o identificador do método.
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Observação

Não há suporte para chamar métodos genéricos abertos com métodos estáticos do .NET, mas há suporte para métodos de instância. Para obter mais informações, confira a seção Chamar métodos de classe genérica do .NET.

No componente a seguir, o método C# ReturnArrayAsync retorna uma matriz int. O atributo [JSInvokable] é aplicado ao método, o que possibilita que o método seja invocador por 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);
}

A funçãoaddHandlersJS adiciona um eventoclick ao botão. A função returnArrayAsyncJS é atribuída como o manipulador.

A função returnArrayAsyncJS chama o método do .NET ReturnArrayAsync do componente, e registra o resultado no console de ferramentas para desenvolvedores Web do navegador. BlazorSample é o nome de assembly do aplicativo.

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

A funçãoaddHandlersJS adiciona um eventoclick ao botão. A função returnArrayAsyncJS é atribuída como o manipulador.

A função returnArrayAsyncJS chama o método do .NET ReturnArrayAsync do componente, e registra o resultado no console de ferramentas para desenvolvedores Web do navegador. BlazorSample é o nome de assembly do aplicativo.

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

O atributo HTML <button> do elemento onclick é a atribuição do manipulador de eventos onclick do JavaScript para processar eventos click, não o atributo de diretiva Blazor do @onclick. A função returnArrayAsyncJS é atribuída como o manipulador.

A função returnArrayAsyncJS a seguir chama o método do .NET ReturnArrayAsync do componente e registra o resultado no console de ferramentas para desenvolvedores Web do navegador. BlazorSample é o nome de assembly do aplicativo.

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

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

Quando o botão Trigger .NET static method é selecionado, a saída do console de ferramentas de desenvolvedor do navegador exibe os dados da matriz. O formato da saída é um pouco diferente entre os navegadores. A saída a seguir mostra o formato usado pelo Microsoft Edge:

Array(3) [ 11, 12, 13 ]

Passe os dados para um método do .NET ao chamar a função invokeMethodAsync passando os dados como argumentos.

Para demonstrar a passagem de dados para o .NET, passe uma posição inicial para o ReturnArrayAsync método em que o método é invocado em 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>

O método invocável ReturnArrayAsync do componente recebe a posição inicial e cria a matriz com base nele. A matriz é retornada para log no console:

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

Depois que o aplicativo é recompilado e o navegador é atualizado, a seguinte saída é exibida no console do navegador quando o botão é selecionado:

Array(3) [ 14, 15, 16 ]

O identificador de método do .NET para a chamada JS é o nome do método do .NET, mas você pode especificar um identificador diferente usando o construtor de atributo [JSInvokable]. No exemplo a seguir, DifferentMethodName é o identificador de método atribuído para o método ReturnArrayAsync:

[JSInvokable("DifferentMethodName")]

Na chamada para DotNet.invokeMethodAsync (componentes do lado do servidor ou do cliente) ou DotNet.invokeMethod (somente componentes do lado do cliente), chame DifferentMethodName para executar o método .NET ReturnArrayAsync:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (somente componentes do lado do cliente)

Observação

O exemplo de método ReturnArrayAsync nesta seção retorna o resultado de um Task, sem o uso das palavras-chave explícitas async e await C#. Codificar métodos com async e await é típico de métodos que usam a palavra-chave await para retornar o valor de operações assíncronas.

Método ReturnArrayAsync composto pelas palavras-chave async e await:

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

Para obter mais informações, confira Programação assíncrona com async e await no guia C#.

Criar referências de dados e objeto JavaScript para passar para o .NET

Chame DotNet.createJSObjectReference(jsObject) para criar uma referência de objeto JS para que possa ser passada para o .NET, onde jsObject é o JS Object usado para criar a referência de objeto JS. O exemplo a seguir passa uma referência ao objeto window não serializável para o .NET, que o recebe no método C# ReceiveWindowObject como um IJSObjectReference:

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

No exemplo anterior, o espaço reservado {ASSEMBLY NAME} é o namespace do aplicativo.

Observação

O exemplo anterior não requer o descarte do JSObjectReference, pois uma referência ao objeto window não é mantida em JS.

Manter uma referência a um JSObjectReference requer seu descarte para evitar o vazamento de memória JS no cliente. O exemplo a seguir refatora o código anterior para capturar uma referência ao JSObjectReference, seguido de uma chamada para o DotNet.disposeJSObjectReference() para descartar a referência:

var jsObjectReference = DotNet.createJSObjectReference(window);

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

DotNet.disposeJSObjectReference(jsObjectReference);

No exemplo anterior, o espaço reservado {ASSEMBLY NAME} é o namespace do aplicativo.

Chame DotNet.createJSStreamReference(streamReference) para criar uma referência de fluxo JS para que possa ser passada para o .NET, onde streamReference que é um ArrayBuffer, Blob ou qualquer matriz de tipo, como Uint8Array ou Float32Array, usada para criar a referência de fluxo JS.

Invocar um método de instância do .NET

Para invocar um método de instância do .NET a partir do JavaScript (JS):

  • Passe a instância do .NET por referência para o JS, encapsulando a instância em um DotNetObjectReference e chamando Create nela.

  • Invoque um método de instância do .NET a partir de JS usando invokeMethodAsync (recomendado) ou invokeMethod (somente componentes do lado do cliente) a partir do DotNetObjectReference passado. Passe o identificador do método de instância do .NET e quaisquer argumentos. A instância do .NET também pode ser passada como um argumento ao invocar outros métodos do .NET a partir de JS.

    No exemplo a seguir:

    • dotNetHelper é um DotNetObjectReference.
    • O espaço reservado {.NET METHOD ID} é o identificador do método do .NET.
    • O espaço reservado {ARGUMENTS} representa os argumentos opcionais separados por vírgulas a serem passados para o método, dentre os quais, cada um deve ser serializável em JSON.
    dotNetHelper.invokeMethodAsync('{.NET METHOD ID}', {ARGUMENTS});
    

    Observação

    invokeMethodAsync e invokeMethod não aceitam um parâmetro de nome de assembly ao invocar um método de instância.

    invokeMethodAsync retorna um JS Promise que representa o resultado da operação. invokeMethod (somente componentes do lado do cliente) retorna o resultado da operação.

    Importante

    Para componentes do lado do servidor, recomendamos a função assíncrona (invokeMethodAsync) em vez da versão síncrona (invokeMethod).

  • Descarte o DotNetObjectReference.

As seções a seguir deste artigo demonstram várias abordagens para invocar um método de instância do .NET:

Evite cortar os métodos do .NET que podem ser invocados por JavaScript

Esta seção se aplica a aplicativos do lado do cliente com compilação AOT e nova vinculação de runtime habilitadas.

Vários dos exemplos nas seções a seguir são baseados em uma abordagem de instância de classe, em que o método do .NET que pode ser invocado por JavaScript marcado com o atributo [JSInvokable] é um membro de uma classe que não é um componente Razor. Quando esses métodos do .NET estão localizados em um componente Razor, eles são protegidos contra nova vinculação/corte de runtime. Para proteger os métodos do .NET contra o corte fora dos componentes Razor, implemente os métodos com o atributo DynamicDependency no construtor da classe, como demonstra o exemplo a seguir:

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

public class ExampleClass {

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

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

Para obter mais informações, confira Preparar bibliotecas do .NET para corte: DynamicDependency.

Passar um DotNetObjectReference para uma função JavaScript individual

O exemplo nesta seção demonstra como passar um DotNetObjectReference para uma função JavaScript individual (JS).

A função sayHello1JS a seguir recebe um DotNetObjectReference e chama o invokeMethodAsync para chamar o método do .NET GetHelloMessage de um componente:

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

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

No componente a seguir:

  • O componente tem um método do .NET que pode ser invocado por JS chamado GetHelloMessage.
  • Quando o botão Trigger .NET instance method é selecionado, a função JS do sayHello1 é chamada com o DotNetObjectReference.
  • sayHello1:
    • Chama GetHelloMessage e recebe o resultado da mensagem.
    • Retorna o resultado da mensagem para o método de chamada TriggerDotNetInstanceMethod.
  • A mensagem retornada de sayHello1 em result será exibida para o usuário.
  • Para evitar uma perda de memória e permitir a coleta de lixo, a referência de objeto do .NET criada por DotNetObjectReference é descartada no método 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();
    }
}

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

Use as seguintes diretrizes para passar argumentos para um método de instância:

Adicione parâmetros à invocação do método do .NET. No exemplo a seguir, um nome é passado para o método. Adicione mais parâmetros à lista, conforme necessário.

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

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

Forneça a lista de parâmetros para o método do .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();
    }
}

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

Passar um DotNetObjectReference para uma classe com várias funções JavaScript

O exemplo nesta seção demonstra como passar um DotNetObjectReference para uma classe JavaScript (JS) com várias funções.

Crie e passe um DotNetObjectReference a partir do método de ciclo de vida OnAfterRenderAsync para uma classe JS para várias funções usarem. Verifique se o código do .NET descarta o DotNetObjectReference, como mostra o exemplo a seguir.

No componente a seguir, os botões Trigger JS function chamam as funções JS definindo a propriedade JSonclick, não o atributo de diretiva @onclick de Blazor.

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

No exemplo anterior:

  • JS é uma instância injetada IJSRuntime. IJSRuntime é registrado pela estrutura Blazor.
  • O nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.
  • O componente deve descartar explicitamente o DotNetObjectReference para permitir a coleta de lixo e evitar uma perda de memória.

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

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

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

No exemplo anterior:

  • JS é uma instância injetada IJSRuntime. IJSRuntime é registrado pela estrutura Blazor.
  • O nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.
  • O componente deve descartar explicitamente o DotNetObjectReference para permitir a coleta de lixo e evitar uma perda de memória.
<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>

No exemplo anterior:

  • A classe GreetingHelpers é adicionada ao objeto window para definir globalmente a classe, o que permite que Blazor localize a classe para interoperabilidade JS.
  • O nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

Chamar os métodos de classe genérica do .NET

As funções JavaScript (JS) podem chamar os métodos de classe genérica do .NET, onde uma função JS chama um método do .NET de uma classe genérica.

Na seguinte classe de tipo genérico (GenericType<TValue>):

  • A classe tem um único parâmetro de tipo (TValue) com uma única propriedade genérica Value.
  • A classe tem dois métodos não genéricos marcados com o atributo [JSInvokable], cada um com um parâmetro de tipo genérico chamado newValue:
    • Update atualiza de forma síncrona o valor de Value a partir de newValue.
    • UpdateAsync atualiza de forma assíncrona o valor de Value a partir de newValue depois de criar uma tarefa aguardável com Task.Yield, que retorna de forma assíncrona para o contexto atual, quando aguardada.
  • Cada um dos métodos de classe grava o tipo de TValue e o valor de Value no console. Gravar no console é apenas para fins de demonstração. Os aplicativos de produção geralmente evitam gravar no console em favor do log do aplicativo. Para obter mais informações, confira Registro em log Blazor do ASP.NET Core e Registro em log no .NET Core e ASP.NET Core.

Observação

Tipos e métodos genéricos abertos não especificam tipos para espaços reservados de tipo. Por outro lado, genéricos fechados fornecem tipos para todos os espaços reservados de tipo. Os exemplos nesta seção demonstram genéricos fechados, mas a invocação de JSmétodos de instância de interoperabilidade de com genéricos abertos é compatível. Não é possível usar genéricos abertos para as invocações do método estático do .NET, que foram descritas anteriormente neste artigo.

Para obter mais informações, consulte os seguintes artigos:

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

Na função invokeMethodsAsync a seguir:

  • Os métodos Update e UpdateAsync da classe de tipo genérico são chamados com argumentos que representam cadeias de caracteres e números.
  • Os componentes do lado do cliente dão suporte aos métodos de chamada do .NET de forma síncrona com invokeMethod. syncInterop recebe um valor booliano que indica se a interoperabilidade JS está ocorrendo no cliente. Quando syncInterop é true, invokeMethod é chamado com segurança. Se o valor de syncInterop e false, somente a função assíncrona invokeMethodAsync será chamada porque a interoperabilidade JS está em execução em um componente do lado do servidor.
  • Para fins de demonstração, a chamada de função DotNetObjectReference (invokeMethod ou invokeMethodAsync), o método do .NET chamado (Update ou UpdateAsync) e o argumento são gravados no console. Os argumentos usam um número aleatório para permitir a correspondência da chamada de função JS com a invocação do método do .NET (também gravada no console no lado do .NET). O código de produção geralmente não grava no console, seja no cliente ou no servidor. Os aplicativos de produção geralmente dependem do log do aplicativo. Para obter mais informações, confira Registro em log Blazor do ASP.NET Core e Registro em log no .NET Core e 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>

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

No seguinte componente GenericsExample:

  • A JS da função invokeMethodsAsync é chamada quando o botão Invoke Interop é selecionado.
  • Um par de tipos DotNetObjectReference é criado e passado para a função JS para instâncias do GenericType como um string e um 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();
    }
}

No exemplo anterior, JS é uma instância injetada IJSRuntime. IJSRuntime é registrado pela estrutura Blazor.

O seguinte demonstra a saída típica do exemplo anterior quando o botão Invoke Interop é selecionado em um componente do lado do cliente:

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

Se o exemplo anterior for implementado em um componente do lado do servidor, as chamadas síncronas com invokeMethod serão evitadas. Para componentes do lado do servidor, recomendamos a função assíncrona (invokeMethodAsync) em vez da versão síncrona (invokeMethod).

Saída típica de um componente do lado do servidor:

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

Os exemplos de saída anteriores demonstram que métodos assíncronos são executados e concluídos em uma ordem arbitrária, dependendo de vários fatores, incluindo o agendamento de thread e a velocidade da execução do método. Não é possível prever de forma confiável a ordem de conclusão para chamadas de método assíncrono.

Exemplos de instância de classe

A seguinte função de sayHello1JS:

  • Chama o método do .NET GetHelloMessage no DotNetObjectReference passado.
  • Retorna a mensagem de GetHelloMessage para o chamador sayHello1.
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

A classe HelloHelper a seguir tem um método do .NET que pode ser invocado por JS chamado GetHelloMessage. Quando HelloHelper é criado, o nome na Name propriedade é usado para retornar uma mensagem de 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}!";
}

O método CallHelloHelperGetHelloMessage na classe JsInteropClasses3 a seguir invoca o JS da função sayHello1 com uma nova instância de 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);
    }
}

Para evitar um vazamento de memória e permitir a coleta de lixo, a referência de objeto do .NET criada por DotNetObjectReference é descartada, quando a referência de objeto sai do escopo com a sintaxe using var.

Quando o botão Trigger .NET instance method é selecionado no componente a seguir, JsInteropClasses3.CallHelloHelperGetHelloMessage é chamado com o valor de 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);
    }
}

A imagem a seguir mostra o componente renderizado com o nome Amy Pond no campo Name. Depois que o botão é selecionado, Hello, Amy Pond! é exibido na interface do usuário:

Exemplo do componente 'CallDotNetExample4' renderizado

O padrão anterior mostrado na classe JsInteropClasses3 também pode ser implementado inteiramente em um componente.

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

Para evitar um vazamento de memória e permitir a coleta de lixo, a referência de objeto do .NET criada por DotNetObjectReference é descartada, quando a referência de objeto sai do escopo com a sintaxe using var.

A saída exibida pelo componente é Hello, Amy Pond!, quando o nome Amy Pond é fornecido no campo name.

No componente anterior, a referência de objeto do .NET é descartada. Se uma classe ou componente não descartar o DotNetObjectReference, descarte-o no cliente chamando dispose no DotNetObjectReference passado:

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

No exemplo anterior:

  • O espaço reservado {JS FUNCTION NAME} é o nome da função JS.
  • O nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.
  • O espaço reservado {.NET METHOD ID} é o identificador do método do .NET.

Classe auxiliar do método de instância do .NET do componente

Uma classe auxiliar pode invocar um método de instância do .NET como um Action. As classes auxiliares são úteis em cenários em que o uso de métodos .NET estáticos não é aplicável:

  • Quando vários componentes do mesmo tipo são renderizados na mesma página.
  • Em aplicativos do lado do servidor com vários usuários simultaneamente usando o mesmo componente.

No exemplo a seguir:

  • O componente contém vários ListItem1 componentes.
  • Cada componente ListItem1 consiste em uma mensagem e um botão.
  • Quando um botão de componente ListItem1 é selecionado, esse método ListItem1 do UpdateMessage altera o texto do item de lista e oculta o botão.

A classe MessageUpdateInvokeHelper a seguir mantém um método do .NET que pode invocado por JS, UpdateMessageCaller, para invocar o Action especificado quando a classe é instanciada.

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

A função updateMessageCallerJS a seguir invoca o método do .NET UpdateMessageCaller.

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

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

O componente ListItem1 a seguir é um componente compartilhado que pode ser usado várias vezes em um componente pai e cria itens de lista (<li>...</li>) para uma lista HTML (<ul>...</ul> ou <ol>...</ol>). Cada instância ListItem1 de componente estabelece uma instância de MessageUpdateInvokeHelper com um Action definido como o método UpdateMessage.

Quando o botão ListItem1 de um componente InteropCall é selecionado, updateMessageCaller é invocado com um DotNetObjectReference criado para a instância MessageUpdateInvokeHelper. Isso permite que a estrutura chame UpdateMessageCaller na instância ListItem1 desse MessageUpdateInvokeHelper. O DotNetObjectReference passado é descartado em 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 é chamado para atualizar a interface do usuário quando message é definido em UpdateMessage. Se StateHasChanged não for chamado, Blazor não terá como saber que a interface do usuário deve ser atualizada quando o Action for invocado.

O componente pai a seguir inclui quatro itens de lista, cada um é uma instância do componente 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>

A imagem a seguir mostra o componente pai renderizado após a seleção do segundo botão InteropCall:

  • O segundo componente ListItem1 exibiu a mensagem UpdateMessage Called!.
  • O botão InteropCall para o segundo componente ListItem1 não está visível porque a propriedade display do CSS do botão está definida como none.

Exemplo do componente 'CallDotNetExample6' renderizado

Método de instância do .NET do componente chamado a partir do DotNetObjectReference atribuído a uma propriedade de elemento

A atribuição de um DotNetObjectReference a uma propriedade de um elemento HTML permite os métodos de chamada do .NET em uma instância de componente:

Semelhante à abordagem descrita na seção Classe auxiliar do método .NET da instância do componente, essa abordagem é útil em cenários em que o uso de métodos .NET estáticos não é aplicável:

  • Quando vários componentes do mesmo tipo são renderizados na mesma página.
  • Em aplicativos do lado do servidor com vários usuários simultaneamente usando o mesmo componente.
  • O método do .NET é invocado a partir de um evento JS (por exemplo, onclick), não de um evento Blazor (por exemplo, @onclick).

No exemplo a seguir:

  • O componente contém váriosListItem2 componentes , que é componente compartilhado.
  • Cada componente ListItem2 consiste em uma mensagem de item de lista <span> e uma segunda <span> com uma propriedade do CSS display definida como inline-block para exibição.
  • Quando um item de lista de componentes ListItem2 é selecionado, esse método ListItem2 do UpdateMessage altera o texto do item de lista no primeiro <span> e oculta o segundo <span>, definindo a display propriedade como none.

A função assignDotNetHelperJS a seguir atribui o DotNetObjectReference a um elemento em uma propriedade chamada dotNetHelper. A função interopCallJS a seguir usa o DotNetObjectReference para que o elemento passado invoque um método do .NET chamado 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>

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

No exemplo anterior, o nome da variável dotNetHelper é arbitrário e pode ser alterado para qualquer nome preferencial.

O componente ListItem2 a seguir é um componente compartilhado que pode ser usado várias vezes em um componente pai e cria itens de lista (<li>...</li>) para uma lista HTML (<ul>...</ul> ou <ol>...</ol>).

Cada instância de componente ListItem2 invoca a função assignDotNetHelperJS em OnAfterRenderAsync com uma referência de elemento (o primeiro elemento <span> do item de lista) e a instância do componente como um DotNetObjectReference.

Quando a mensagem ListItem2 de um componente <span> é selecionada, interopCall é invocado passando o elemento <span> como um parâmetro (this), que invoca o método do .NET UpdateMessage. No UpdateMessage, StateHasChanged é chamado para atualizar a interface do usuário quando message é definido e a propriedade display do segundo <span> é atualizada. Se StateHasChanged não for chamado, Blazor não terá como saber que a interface do usuário deve ser atualizada quando o método for invocado.

O DotNetObjectReference é descartado quando o componente é descartado.

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

O componente pai a seguir inclui quatro itens de lista, cada um é uma instância do componente 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>

Interoperabilidade JS síncrona em componentes do lado do cliente

Esta seção se aplica somente a componentes do lado do cliente.

As chamadas de interoperabilidade de JS são assíncronas, independentemente do código chamado ser síncrono ou assíncrono. As chamadas são assíncronas para garantir que os componentes sejam compatíveis nos modos de renderização do lado do servidor e do lado cliente. No servidor, todas as chamadas de interoperabilidade de JS devem ser assíncronas porque são enviadas por uma conexão de rede.

Se tiver certeza de que seu componente só é executado no WebAssembly, você pode optar por fazer chamadas de interoperabilidade de JS síncronas. Isso tem um pouco menos sobrecarga do que fazer chamadas assíncronas e pode resultar em menos ciclos de renderização, pois não há estado intermediário enquanto aguarda resultados.

Para fazer uma chamada síncrona do JavaScript para o .NET em um componente do lado do cliente, use DotNet.invokeMethod em vez de DotNet.invokeMethodAsync.

As chamadas síncronas funcionarão se:

  • O componente só é renderizado para execução no WebAssembly.
  • A função chamada retorna um valor de forma síncrona. A função não é um método async e não retorna um Task do .NET ou um Promise do JavaScript.

Localização do JavaScript

Carregue o código JavaScript (JS) usando qualquer uma das abordagens descritas no artigo sobre a localização do JavaScript:

O uso de módulos JS para carregar JS é descrito neste artigo na seção Isolamento de JavaScript em módulos JavaScript.

Aviso

Coloque apenas uma tag <script> em um arquivo de componente (.razor), caso o componente tenha a garantia de adotar a SSR estática (renderização estática do lado do servidor) porque a tag <script> não pode ser atualizada de forma dinâmica.

Aviso

Não coloque uma marca <script> em um arquivo de componente (.razor) porque a marca <script> não pode ser atualizada dinamicamente.

Isolamento de JavaScript em módulos JavaScript

Blazor habilita o isolamento de JavaScript (JS) em módulos JavaScript padrão (especificação ECMAScript). O carregamento do módulo JavaScript funciona da mesma maneira em Blazor que para outros tipos de aplicativos Web, e você é livre para personalizar como os módulos são definidos em seu aplicativo. Para obter um guia sobre como usar módulos JavaScript, confira Docs da Web do MDN: módulos JavaScript.

O isolamento de JS oferece os seguintes benefícios:

  • O JS importado não polui mais o namespace global.
  • Os consumidores de uma biblioteca e componentes não precisam importar o JS relacionado.

Para obter mais informações, consulte Chamar funções JavaScript de métodos .NET no ASP.NET Core Blazor.

Há suporte para importação dinâmica com o import() operador com ASP.NET Core e Blazor:

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

No exemplo anterior, o espaço reservado {CONDITION} representa uma verificação condicional para determinar se o módulo deve ser carregado.

Para obter compatibilidade com o navegador, confira Posso usar: módulos JavaScript: importação dinâmica.

Evitar referências de objeto circular

Os objetos que contêm referências circulares não podem ser serializados no cliente para:

  • Chamadas de método do .NET.
  • O método do JavaScript chama a partir do C# quando o tipo de retorno tem referências circulares.

Suporte à matriz de bytes

Blazor dá suporte à interoperabilidade do JavaScript (JS) da matriz de bytes otimizada que evita codificar/decodificar matrizes de bytes em Base64. O exemplo a seguir usa a interoperabilidade do JS para passar uma matriz de bytes para o .NET.

Forneça uma função sendByteArrayJS. A função é chamada estaticamente, o que inclui o parâmetro de nome de assembly na chamada invokeMethodAsync, usando um botão no componente, e não retorna um valor:

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>

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

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

Para obter informações sobre como usar uma matriz de bytes ao chamar JavaScript a partir do .NET, confira Chamar funções JavaScript a partir dos métodos do .NET no Blazor do ASP.NET Core.

Transmitir do JavaScript para o .NET

Blazor dá suporte a dados de streaming diretamente do JavaScript para o .NET. Os fluxos são solicitados usando a interface Microsoft.JSInterop.IJSStreamReference.

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync retorna um Stream e usa os seguintes parâmetros:

  • maxAllowedSize: número máximo de bytes permitidos para a operação de leitura do JavaScript, que usa o padrão 512.000 bytes, se não especificado.
  • cancellationToken: um CancellationToken para cancelar a leitura.

No JavaScript:

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

No código 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);

No exemplo anterior:

  • JS é uma instância injetada IJSRuntime. IJSRuntime é registrado pela estrutura Blazor.
  • O dataReferenceStream é gravado em disco (file.txt) no caminho da pasta temporária do usuário atual (GetTempPath).

Chamar as funções do JavaScript a partir de métodos do .NET no Blazor do ASP.NET Core abrange a operação inversa, transmitindo do .NET para o JavaScript usando um DotNetStreamReference.

Os uploads de arquivos Blazor do ASP.NET Core abrangem como carregar um arquivo no Blazor. Para obter um exemplo de formulários que transmite dados <textarea> em um componente do lado do servidor, consulte Solucionar problemas de formulários do ASP.NET Core Blazor.

Interoperabilidade [JSImport]/[JSExport] JavaScript

Esta seção se aplica a componentes do lado do cliente.

Como alternativa à interação com o JavaScript (JS) em componentes do lado do cliente que usam o mecanismo de interoperabilidade JS de Blazor com base na interface IJSRuntime, uma API de interoperabilidade JS[JSImport]/[JSExport] está disponível para aplicativos direcionados ao .NET 7 ou posterior.

Para obter mais informações, consulte Interop de JSImport/JSExport de JavaScript com o ASP.NET Core Blazor.

Descarte de referências de objeto de interoperabilidade do JavaScript

Os exemplos em todos os artigos de interoperabilidade do JavaScript (JS) demonstram padrões típicos de descarte de objetos:

As referências de objeto de interoperabilidade JS são implementadas como um mapa com chave de um identificador no lado da chamada de interoperabilidade JS que cria a referência. Quando o descarte de objetos é iniciado no .NET ou do lado do JS, Blazor remove a entrada do mapa e o objeto pode ser coletado como lixo, desde que nenhuma outra referência forte ao objeto esteja presente.

No mínimo, sempre descarte objetos criados no lado do .NET para evitar o vazamento de memória gerenciada do .NET.

Tarefas de limpeza do DOM no descarte de componentes

Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).

Chamadas de interoperabilidade do JavaScript sem um circuito

Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).

Recursos adicionais