Compartir a través de


Llamada a métodos de .NET desde funciones de JavaScript en ASP.NET Core Blazor

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

En este artículo se explica cómo invocar métodos de .NET desde JavaScript (JS).

Para obtener información sobre cómo llamar a funciones de JS desde .NET, consulta Llamada a funciones de JavaScript con métodos de .NET en Blazor de ASP.NET Core.

Invocación de un método de .NET estático

Para invocar un método de .NET estático desde JavaScript (JS), usa las funciones de JS:

  • DotNet.invokeMethodAsync (recomendado): asincrónico para los componentes del lado servidor y del lado cliente.
  • DotNet.invokeMethod: sincrónico solo para los componentes del lado cliente.

Pasa el nombre del ensamblado que contiene el método, el identificador del método estático de .NET y cualquier argumento.

En el ejemplo siguiente:

  • El marcador de posición {ASSEMBLY NAME} es el nombre de ensamblado de la aplicación.
  • El marcador de posición {.NET METHOD ID} es el identificador del método de .NET.
  • El marcador de posición {ARGUMENTS} son argumentos opcionales separados por comas que se pasan al método, y cada uno de ellos debe ser serializable con JSON.
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync devuelve un JS Promise que representa el resultado de la operación. DotNet.invokeMethod (componentes del lado cliente) devuelve el resultado de la operación.

Importante

En el caso de los componentes del lado servidor, se recomienda la función asincrónica (invokeMethodAsync) a través de la versión sincrónica (invokeMethod).

El método de .NET debe ser público y estático, y debe tener el atributo [JSInvokable].

En el ejemplo siguiente:

  • El marcador de posición {<T>} indica el tipo de valor devuelto, que solo es necesario para los métodos que devuelven un valor.
  • El marcador de posición {.NET METHOD ID} es el identificador del método.
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Nota

La llamada a métodos genéricos abiertos no se admite con métodos estáticos de .NET, pero se admite con métodos de instancia. Para obtener más información, consulta la sección Llamada a métodos de clase genérica de .NET.

En el siguiente componente, el método de C# ReturnArrayAsync devuelve una matriz int. El atributo [JSInvokable] se aplica al método, lo que hace que el método sea invocable 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);
}

La función addHandlersJS agrega un evento click al botón. La función returnArrayAsyncJS se asigna como controlador.

La función returnArrayAsyncJS llama al método ReturnArrayAsync .NET del componente, que registra el resultado en la consola de herramientas para desarrolladores web del explorador. BlazorSample es el nombre del ensamblado de la aplicación.

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

La función addHandlersJS agrega un evento click al botón. La función returnArrayAsyncJS se asigna como controlador.

La función returnArrayAsyncJS llama al método ReturnArrayAsync .NET del componente, que registra el resultado en la consola de herramientas para desarrolladores web del explorador. BlazorSample es el nombre del ensamblado de la aplicación.

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

El atributo HTML onclick del elemento <button> es la asignación del controlador de eventos onclick de JavaScript para procesar eventos click, y no el atributo de directiva @onclick de Blazor. La función returnArrayAsyncJS se asigna como controlador.

La siguiente función returnArrayAsyncJS, llama al método ReturnArrayAsync .NET del componente, que registra el resultado en la consola de herramientas para desarrolladores web del explorador. BlazorSample es el nombre del ensamblado de la aplicación.

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

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

Cuando se selecciona el botón Trigger .NET static method , la salida de la consola de las herramientas de desarrollo del explorador muestra los datos de la matriz. El formato de la salida difiere ligeramente entre los exploradores. En la salida siguiente se muestra el formato utilizado por Microsoft Edge:

Array(3) [ 11, 12, 13 ]

Para pasar datos a un método .NET al llamar a la función invokeMethodAsync, páselos como argumentos.

Para mostrar el paso de datos a .NET, pasa una posición inicial al método ReturnArrayAsync donde se invoca al método en 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>

El método invocable ReturnArrayAsync del componente recibe la posición inicial y construye la matriz a partir de él. La matriz se devuelve para el registro en la consola:

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

Una vez que se vuelve a compilar la aplicación y se actualiza el explorador, la salida siguiente aparece en la consola del explorador cuando se selecciona el botón:

Array(3) [ 14, 15, 16 ]

El identificador de método de .NET para la llamada de JS es el nombre del método de .NET, pero puedes especificar un identificador distinto mediante el constructor del atributo [JSInvokable]. En el ejemplo siguiente, DifferentMethodName es el identificador del método asignado para el método ReturnArrayAsync:

[JSInvokable("DifferentMethodName")]

En la llamada a DotNet.invokeMethodAsync (componentes del lado servidor o del lado cliente) o DotNet.invokeMethod (solo componentes del lado cliente), llama DifferentMethodName a para ejecutar el ReturnArrayAsync método .NET:

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (sólo componentes del lado del cliente)

Nota:

El ejemplo de método ReturnArrayAsync de esta sección devuelve el resultado de un Task sin el uso de las palabras clave async y await de C# explícitas. La codificación de métodos con async y await es típica de los métodos que usan la palabra clave await para devolver el valor de las operaciones asincrónicas.

El método ReturnArrayAsync compuesto con las palabras clave async y await:

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

Para obtener más información, consulta Programación asincrónica con async y await en la guía de C#.

Creación de referencias de datos y objetos de JavaScript para pasar a .NET

Llama a DotNet.createJSObjectReference(jsObject) para construir una referencia de objeto de JS para que se pueda pasar a .NET, donde jsObject es el JS Object utilizado para crear la referencia de objeto de JS. En el ejemplo siguiente se pasa una referencia al objeto window no serializable a .NET, que lo recibe en el método de C# ReceiveWindowObject como IJSObjectReference:

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

En el ejemplo anterior, el marcador de posición {ASSEMBLY NAME} es el nombre de espacio de la aplicación.

Nota

El ejemplo anterior no requiere la eliminación de JSObjectReference, ya que una referencia al objeto window no se mantiene en JS.

Mantener una referencia a JSObjectReference requiere eliminarla para evitar la fuga de memoria de JS en el cliente. En el ejemplo siguiente se refactoriza el código anterior para capturar una referencia a JSObjectReference, seguida de una llamada a DotNet.disposeJSObjectReference() para eliminar la referencia:

var jsObjectReference = DotNet.createJSObjectReference(window);

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

DotNet.disposeJSObjectReference(jsObjectReference);

En el ejemplo anterior, el marcador de posición {ASSEMBLY NAME} es el nombre de espacio de la aplicación.

Llama a DotNet.createJSStreamReference(streamReference) para construir una referencia de flujo de JS de modo que se pueda pasar a .NET, donde streamReference es ArrayBuffer, Blob o cualquier matriz con tipo, como Uint8Array o Float32Array, que se usa para crear la referencia de flujo de JS.

Invocación de un método de .NET de instancia

Para invocar un método de .NET de instancia desde JavaScript (JS):

  • Pasa la instancia de .NET por referencia a JS encapsulando la instancia en un DotNetObjectReference y llamando a Create en ella.

  • Invoca un método de instancia de .NET desde JS el uso invokeMethodAsync (recomendado) o invokeMethod (solo componentes del lado cliente) desde el objeto pasado DotNetObjectReference. Pasa el identificador del método .NET de instancia y todos los argumentos. La instancia de .NET también se puede pasar como argumento al invocar otros métodos de .NET desde JS.

    En el ejemplo siguiente:

    • dotNetHelper es un objeto DotNetObjectReference.
    • El marcador de posición {.NET METHOD ID} es el identificador del método de .NET.
    • El marcador de posición {ARGUMENTS} son argumentos opcionales separados por comas que se pasan al método, y cada uno de ellos debe ser serializable con JSON.
    dotNetHelper.invokeMethodAsync('{.NET METHOD ID}', {ARGUMENTS});
    

    Nota:

    invokeMethodAsync y invokeMethod no aceptan un parámetro de nombre de ensamblado al invocar un método de instancia.

    invokeMethodAsync devuelve un JS Promise que representa el resultado de la operación. invokeMethod (solo componentes del lado cliente) devuelve el resultado de la operación.

    Importante

    En el caso de los componentes del lado servidor, se recomienda la función asincrónica (invokeMethodAsync) a través de la versión sincrónica (invokeMethod).

  • Deseche DotNetObjectReference.

En las siguientes secciones de este artículo muestran varios enfoques para invocar un método de .NET de instancia:

Prevención del recorte de los métodos de .NET invocables de JavaScript

Esta sección se aplica a las aplicaciones del lado del cliente con la compilación anticipada (AOT) y la revinculación en tiempo de ejecución activadas.

Varios de los ejemplos de las secciones siguientes se basan en un enfoque de instancia de clase, donde el método .NET invocable de JavaScript marcado con el atributo [JSInvokable] es un miembro de una clase que no es un componente Razor. Cuando estos métodos de .NET se encuentran en un componente Razor, están protegidos contra la revinculación en tiempo de ejecución o el recorte. Para proteger los métodos de .NET del recorte fuera de los componentes Razor, implementa los métodos con el atributo DynamicDependency en el constructor de la clase, tal como se muestra en el ejemplo siguiente:

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

public class ExampleClass {

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

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

Para obtener más información, consulta Preparación de las bibliotecas de .NET para el recorte: DynamicDependency.

Pasar DotNetObjectReference a una función de JavaScript individual

En el ejemplo de esta sección enseña cómo pasar DotNetObjectReference a una función JavaScript (JS) individual.

La función sayHello1 de JS siguiente recibe DotNetObjectReference y llama a invokeMethodAsync para invocar el método de .NET GetHelloMessage de un componente:

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

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

Para el componente siguiente:

  • El componente tiene un método de .NET invocable por JS llamado GetHelloMessage.
  • Cuando se selecciona el botón Trigger .NET instance method , se llama a la función sayHello1 de JS con DotNetObjectReference.
  • sayHello1:
    • Llama a GetHelloMessage y recibe el resultado del mensaje.
    • Devuelve el resultado del mensaje al método TriggerDotNetInstanceMethod que realiza la llamada.
  • El mensaje devuelto de sayHello1 en result se muestra al usuario.
  • Para evitar una pérdida de memoria y permitir la recolección de elementos no utilizados, la referencia de objeto de .NET creada por DotNetObjectReference se elimina en el 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();
    }
}

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

Usa las instrucciones siguientes para pasar argumentos a un método de instancia:

Agrega parámetros a la invocación del método de .NET. En el ejemplo siguiente, se pasa un nombre al método. Agrega parámetros adicionales a la lista según sea necesario.

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

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

Proporciona la lista de parámetros al método de .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();
    }
}

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

Pasar DotNetObjectReference a una clase con varias funciones de JavaScript

En el ejemplo de esta sección enseña cómo pasar DotNetObjectReference a una clase JavaScript (JS) con varias funciones.

Crea y pasa DotNetObjectReference desde el método de ciclo de vida OnAfterRenderAsync a una clase JS para usar varias funciones. Asegúrate de que el código .NET elimina DotNetObjectReference, como se muestra en el ejemplo siguiente.

En el siguiente componente, los botones Trigger JS function llaman a funciones de JS, para lo que establecen la propiedad onclick de JS, no el atributo de la directiva @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();
    }
}

En el ejemplo anterior:

  • JS es una instancia de IJSRuntime insertada. El marco Blazor registra IJSRuntime.
  • En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.
  • El componente debe eliminar explícitamente DotNetObjectReference para permitir la recolección de elementos no utilizados y evitar una pérdida de memoria.
  • JSDisconnectedException está atrapado durante la eliminación del módulo en caso Blazorde que se pierda el SignalR circuito. Si el código anterior se usa en una Blazor WebAssembly aplicación, no hay conexión SignalR para perder, por lo que puede quitar el bloque y dejar lacatch try-línea que elimina el módulo ().await module.DisposeAsync(); Para obtener más información, consulta Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

CallDotNetExampleOneHelper.razor.js:

export class GreetingHelpers {
  static dotNetHelper;

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

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

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

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

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

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

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

En el ejemplo anterior:

  • JS es una instancia de IJSRuntime insertada. El marco Blazor registra IJSRuntime.
  • En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.
  • El componente debe eliminar explícitamente DotNetObjectReference para permitir la recolección de elementos no utilizados y evitar una pérdida de memoria.
<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>

En el ejemplo anterior:

  • La clase GreetingHelpers se agrega al objeto window para definir globalmente la clase, lo que permite a Blazor buscar la clase para la interoperabilidad de JS.
  • En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

Llamada a métodos de clase genérica de .NET

Las funciones JavaScript (JS) pueden llamar a métodos de clase genérica de .NET, donde una función JS llama a un método .NET de una clase genérica.

En la clase de tipo genérico siguiente (GenericType<TValue>):

  • La clase tiene un único parámetro de tipo (TValue) con una sola propiedad Value genérica.
  • La clase tiene dos métodos no genéricos marcados con el atributo [JSInvokable], cada uno con un parámetro de tipo genérico denominado newValue:
    • Update actualiza sincrónicamente el valor de Value desde newValue.
    • UpdateAsync actualiza de asincrónicamente el valor de Value desde newValue después de crear una tarea por la que se puede esperar con Task.Yield, que vuelve a suspenderse asincrónicamente al contexto actual cuando se espera por dicho elemento.
  • Cada uno de los métodos de clase escribe el tipo de TValue y el valor de Value en la consola. La escritura en la consola solo tiene fines de demostración. Las aplicaciones de producción normalmente evitan escribir en la consola en favor del registro de la aplicación. Para obtener más información, consulta Registro de Blazor en ASP.NET Core y Registro en .NET Core y ASP.NET Core.

Nota

Los tipos y métodos genéricos abiertos no especifican tipos para los marcadores de posición de tipo. Por el contrario, los genéricos cerrados suministran tipos para todos los marcadores de posición de tipo. Los ejemplos de esta sección muestran genéricos cerrados, pero la invocación de interoperabilidad de JS de métodos de instancia con genéricos abiertos se admite. El uso de genéricos abiertos no se admite para las invocaciones de métodos estáticos de .NET, que se describieron anteriormente en este artículo.

Consulta los siguientes artículos para más información:

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

En la siguiente función invokeMethodsAsync:

  • Los métodos Update y UpdateAsync de la clase de tipo genérico se llaman con argumentos que representan cadenas y números.
  • Los componentes del lado cliente admiten la llamada a métodos de .NET de forma sincrónica con invokeMethod. syncInterop recibe un valor booleano que indica si la interoperabilidad JS se está produciendo en el cliente. Cuando syncInterop es true, se llama a invokeMethod de forma segura. Si el valor de syncInterop es false, solo se llama a la función asíncrona invokeMethodAsync porque la interoperabilidad JS se está ejecutando en un componente del lado del servidor.
  • Con fines de demostración, la llamada de función DotNetObjectReference (invokeMethod o invokeMethodAsync), el método de .NET denominado (Update o UpdateAsync) y el argumento se escriben en la consola. Los argumentos usan un número aleatorio para permitir que la función JS llame a la invocación del método .NET (también escrita en la consola en el lado de .NET). Normalmente, el código de producción no escribe en la consola, ni en el cliente ni en el servidor. Las aplicaciones de producción normalmente se basan en el registro de la aplicación. Para obtener más información, consulta Registro de Blazor en ASP.NET Core y Registro en .NET Core y 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>

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

En el componente GenericsExample siguiente:

  • Se llama a la función JSinvokeMethodsAsync cuando se selecciona el botón Invoke Interop.
  • Se crea un par de tipos DotNetObjectReference y se pasan a la función JS para las instancias de GenericType como string y 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();
    }
}

En el ejemplo anterior, JS es una instancia de IJSRuntime insertada. El marco Blazor registra IJSRuntime.

A continuación se muestra la salida típica del ejemplo anterior cuando se selecciona el botón Invoke Interop en un componente del lado del 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

Si el ejemplo anterior se implementa en un componente del lado del servidor, se evitan las llamadas síncronas con invokeMethod. En el caso de los componentes del lado servidor, se recomienda la función asincrónica (invokeMethodAsync) a través de la versión sincrónica (invokeMethod).

Salida típica de un componente del lado 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

Los ejemplos de salida anteriores muestran que los métodos asincrónicos se ejecutan y completan en un orden arbitrario en función de varios factores, incluida la programación de subprocesos y la velocidad de ejecución del método. No es posible predecir de forma confiable el orden de finalización de las llamadas a métodos asincrónicos.

Ejemplos de instancias de clase

La siguiente función sayHello1JS:

  • Llama al método de .NET GetHelloMessage en la clase DotNetObjectReference pasada.
  • Devuelve el mensaje de GetHelloMessage al autor de la llamada sayHello1.
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

La clase HelloHelper siguiente tiene un método de .NET invocable por JS llamado GetHelloMessage. Cuando se crea HelloHelper, el nombre de la propiedad Name se usa para devolver un mensaje 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}!";
}

El método CallHelloHelperGetHelloMessage de la clase JsInteropClasses3 siguiente invoca la función sayHello1 de JS con una nueva instancia 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 una pérdida de memoria y permitir la recolección de elementos no utilizados, la referencia de objeto de .NET creada por DotNetObjectReference se elimina cuando la referencia de objeto sale del ámbito con la sintaxis using var.

Cuando se selecciona el botón Trigger .NET instance method en el siguiente componente, se llama a JsInteropClasses3.CallHelloHelperGetHelloMessage con el 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);
    }
}

En la imagen siguiente se muestra el componente representado con el nombre Amy Pond en el campo Name. Una vez seleccionado el botón, Hello, Amy Pond! se muestra en la interfaz de usuario:

Ejemplo del componente

El patrón anterior que se muestra en la clase JsInteropClasses3 también se puede implementar íntegramente en un 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 una pérdida de memoria y permitir la recolección de elementos no utilizados, la referencia de objeto de .NET creada por DotNetObjectReference se elimina cuando la referencia de objeto sale del ámbito con la sintaxis using var.

La salida que muestra el componente es Hello, Amy Pond! cuando se especifica el nombre Amy Pond en el campo name.

En el componente anterior, se elimina la referencia al objeto de .NET. Si una clase o un componente no elimina DotNetObjectReference, deséchalo del cliente llamando a dispose en la clase DotNetObjectReference pasada:

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

En el ejemplo anterior:

  • El marcador de posición {JS FUNCTION NAME} es el nombre de la función JS.
  • En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.
  • El marcador de posición {.NET METHOD ID} es el identificador del método de .NET.

Clase auxiliar del método de .NET de la instancia de componente

Una clase auxiliar puede invocar un método de instancia de .NET como Action. Las clases auxiliares son útiles en escenarios en los que no se aplican los métodos estáticos de .NET:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En aplicaciones del lado del servidor con varios usuarios utilizando simultáneamente el mismo componente.

En el ejemplo siguiente:

  • El componente contiene varios ListItem1 componentes.
  • Cada componente ListItem1 consta de un mensaje y un botón.
  • Cuando se selecciona un botón de componente ListItem1, el método UpdateMessage de ese objeto ListItem1 cambia el texto del elemento de lista y oculta el botón.

La clase MessageUpdateInvokeHelper siguiente mantiene un método de .NET invocable por JS, UpdateMessageCaller, para invocar el elemento Action especificado cuando se crea una instancia de la clase.

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

La siguiente función updateMessageCallerJS invoca el método de .NET UpdateMessageCaller.

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

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

El componente ListItem1 siguiente es un componente compartido que se puede usar varias veces en un componente primario y crea elementos de lista (<li>...</li>) para una lista HTML (<ul>...</ul> o <ol>...</ol>). Cada instancia de componente ListItem1 establece una instancia de MessageUpdateInvokeHelper con un elemento Action establecido en su método UpdateMessage.

Cuando se selecciona un botón InteropCall del componente ListItem1, se invoca a updateMessageCaller con un elemento DotNetObjectReference creado para la instancia MessageUpdateInvokeHelper. Esto permite que el marco llame a UpdateMessageCaller en esa instancia MessageUpdateInvokeHelper de ListItem1. La clase DotNetObjectReference pasada se elimina en 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();
    }
}

Se llama a StateHasChanged para actualizar la interfaz de usuario cuando message se establece en UpdateMessage. Si no se llama a StateHasChanged, Blazor no tiene ninguna manera de saber que la interfaz de usuario debe actualizarse cuando se invoca a Action.

El siguiente componente principal incluye cuatro elementos de lista, y cada uno de ellos es una instancia del 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>

En la imagen siguiente se muestra el componente principal representado después de que se selecciona el segundo botón InteropCall:

  • El segundo componente ListItem1 ha mostrado el mensaje UpdateMessage Called!.
  • El botón InteropCall del segundo componente ListItem1 no es visible porque la propiedad display de CSS del botón está establecida en none.

Ejemplo del componente

Método .NET de la instancia de componente al que se llama desde DotNetObjectReference asignado a una propiedad de elemento

La asignación de un objeto DotNetObjectReference a una propiedad de un elemento HTML permite la llamada a métodos .NET en una instancia de componente:

De forma similar al enfoque descrito en la sección Clase auxiliar del método .NET de instancia de componente, este enfoque es útil en escenarios en los que no se aplican los métodos estáticos de .NET:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En aplicaciones del lado del servidor con varios usuarios utilizando simultáneamente el mismo componente.
  • El método .NET se invoca desde un evento JS (por ejemplo, onclick), no desde un evento Blazor (por ejemplo, @onclick).

En el ejemplo siguiente:

  • El componente contiene varios componentes ListItem2, que es un componente compartido.
  • Cada componente ListItem2 consta de un mensaje de elemento de lista <span> y de un segundo elemento <span> con una propiedad CSS display establecida en inline-block para la presentación.
  • Cuando se selecciona un elemento de lista de componentes ListItem2, el método ListItem2 de ese UpdateMessage cambia el texto del elemento de lista en la primera instancia de <span> y oculta la segunda instancia de <span> mediante el establecimiento de su propiedad display en none.

La siguiente función assignDotNetHelperJS asigna DotNetObjectReference a un elemento de una propiedad denominada dotNetHelper. La siguiente función interopCallJS usa DotNetObjectReference del elemento pasado para invocar un método .NET denominado 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>

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

En el ejemplo anterior, el nombre de la variable dotNetHelper es arbitrario y se puede cambiar a cualquier nombre preferido.

El componente ListItem2 siguiente es un componente compartido que se puede usar varias veces en un componente primario y crea elementos de lista (<li>...</li>) para una lista HTML (<ul>...</ul> o <ol>...</ol>).

Cada instancia del componente ListItem2 invoca la función JSassignDotNetHelper en OnAfterRenderAsync con una referencia de elementos (el primer elemento <span> del elemento de lista) y la instancia del componente como DotNetObjectReference.

Cuando se selecciona el elemento <span> de mensaje de un componente ListItem2, se invoca interopCall al pasar el elemento <span> como parámetro (this), que invoca el método .NET UpdateMessage. En UpdateMessage, se llama a StateHasChanged para actualizar la interfaz de usuario cuando se establece message y se actualiza la propiedad display del segundo <span>. Si no se llama a StateHasChanged, Blazor no tiene ninguna forma de saber que la interfaz de usuario debe actualizarse cuando se invoca el método.

El elemento DotNetObjectReference se elimina al eliminar el componente.

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

El siguiente componente principal incluye cuatro elementos de lista, y cada uno de ellos es una instancia del 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>

JS Interoperabilidad sincrónica en componentes del lado cliente

Esta sección solo se aplica a los componentes del lado cliente.

Las llamadas de interoperabilidad de JS son asincrónicas, independientemente de si el código al que se llama es sincrónico o asincrónico. Las llamadas son asíncronas para garantizar que los componentes sean compatibles entre los modos de representación del lado del servidor y del lado del cliente. En el servidor, todas las llamadas de interoperabilidad de JS deben ser asincrónicas porque se envían a través de una conexión de red.

Si sabes con certeza que la aplicación solo se ejecuta en WebAssembly, puedes optar por realizar llamadas de interoperabilidad de JS sincrónicas. Esto tiene una sobrecarga ligeramente menor que la realización de llamadas asincrónicas y puede dar lugar a menos ciclos de representación porque no hay ningún estado intermedio mientras se esperan los resultados.

Para realizar una llamada sincrónica de JavaScript a .NET en un componente del lado cliente, use DotNet.invokeMethod en lugar de DotNet.invokeMethodAsync.

Las llamadas sincrónicas funcionan si:

  • El componente solo se representa para su ejecución en WebAssembly.
  • La función llamada devuelve un valor de forma sincrónica. La función no es un método async y no devuelve un valor Task de .NET o Promise de JavaScript.

Ubicación de JavaScript

Carga el código de JavaScript (JS) mediante cualquiera de los enfoques descritos en el artículo sobre la ubicación de JavaScript:

El uso de módulos JS para cargar JS se describe en este artículo en la sección Aislamiento de JavaScript en módulos de JavaScript.

Advertencia

Coloca solo una etiqueta <script> en un archivo de componente (.razor) si se garantiza que el componente adoptará la representación estática del lado servidor (SSR estática) porque la etiqueta <script> no se puede actualizar de forma dinámica.

Advertencia

No coloques una etiqueta <script> en un archivo de componente (.razor), porque la etiqueta <script> no se puede actualizar dinámicamente.

Aislamiento de JavaScript en módulos de JavaScript

Blazor permite el aislamiento de JavaScript (JS) en módulos de JavaScript estándar (especificación de ECMAScript). La carga de módulos de JavaScript funciona de la misma manera en Blazor que en otros tipos de aplicaciones web, y puede personalizar cómo se definen los módulos en su aplicación. Para obtener una guía sobre cómo usar módulos de JavaScript, consulta Documentación web de MDN: módulos de JavaScript.

El aislamiento de JS proporciona las siguientes ventajas:

  • El JS importado no contamina el espacio de nombres global.
  • No es necesario que los consumidores de una biblioteca y los componentes importen el código de JS relacionado.

Para obtener más información, consulta Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

La importación dinámica con el operador import() se admite con ASP.NET Core y Blazor:

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

En el ejemplo anterior, el marcador de posición {CONDITION} representa una comprobación condicional para determinar si se debe cargar el módulo.

Para la compatibilidad del explorador, consulta ¿Puedo utilizar: Módulos JavaScript: importación dinámica?

Evitar referencias de objetos circulares

Los objetos que contienen referencias circulares no se pueden serializar en el cliente para:

  • Llamadas de método .NET.
  • Llamadas de método JavaScript desde C# cuando el tipo de valor devuelto tiene referencias circulares.

Compatibilidad con matrices de bytes

Blazor admite la interoperabilidad de JavaScript de matriz de bytes optimizada (JS) que evita la codificación o descodificación de matrices de bytes en Base64. En el ejemplo siguiente se usa la interoperabilidad de JS para pasar una matriz de bytes a .NET.

Proporciona una función sendByteArray de JS. La función se llama estáticamente, que incluye el parámetro de nombre de ensamblado en la llamada invokeMethodAsync, mediante un botón en el componente y no devuelve un 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>

Nota:

Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.

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 obtener información sobre el uso de una matriz de bytes al llamar a JavaScript desde .NET, consulta Llamada a funciones de JavaScript desde métodos de .NET en ASP.NET Core Blazor.

Transmisión de JavaScript a .NET

Blazor admite el streaming de datos directamente desde JavaScript a .NET. Los flujos se solicitan mediante la interfaz Microsoft.JSInterop.IJSStreamReference.

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync devuelve un objeto Stream y usa los siguientes parámetros:

  • maxAllowedSize: número máximo de bytes permitidos para la operación de lectura de JavaScript, cuyo valor predeterminado es 512 000 bytes si no se especifica.
  • cancellationToken: un objeto CancellationToken para cancelar la lectura.

En JavaScript:

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

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

En el ejemplo anterior:

  • JS es una instancia de IJSRuntime insertada. El marco Blazor registra IJSRuntime.
  • dataReferenceStream se escribe en el disco (file.txt) en la ruta de la carpeta temporal del usuario actual (GetTempPath).

En Llamada a funciones de JavaScript desde métodos de .NET en ASP.NET Core Blazor se describe la operación inversa, el streaming desde .NET a JavaScript mediante un objeto DotNetStreamReference.

En Cargas de archivos en ASP.NET Core Blazor se describe cómo cargar un archivo en Blazor. Para obtener un ejemplo de formularios que transmite datos <textarea> en un componente del lado servidor, consulta Solución de problemas de formularios de ASP.NET Core Blazor.

Interoperabilidad [JSImport]/[JSExport] de JavaScript

Esta sección se aplica a los componentes del lado cliente.

Como alternativa a la interacción con JavaScript (JS) en los componentes del lado del cliente mediante el mecanismo de interoperabilidad de Blazor's JS basado en la interfaz IJSRuntime, existe una API de interoperabilidad JS[JSImport]/[JSExport] disponible para las aplicaciones orientadas a .NET 7 o posterior.

Para obtener más información, consulta Interoperabilidad JSImport/JSExport de JavaScript con ASP.NET Core Blazor.

Eliminación de referencias de objetos de interoperabilidad de JavaScript

Los ejemplos de los artículos de interoperabilidad de JavaScript (JS) muestran patrones típicos de eliminación de objetos:

Las referencias de objeto de interoperabilidad JS se implementan como un mapa con clave por un identificador en el lado de la llamada de interoperabilidad JS que crea la referencia. Cuando se inicia la eliminación de objetos desde el lado de .NET o JS, Blazor quita la entrada del mapa y el objeto se puede recopilar como elemento no utilizado siempre que no haya ninguna otra referencia sólida al objeto presente.

Como mínimo, elimina siempre los objetos creados en .NET para evitar la pérdida de memoria administrada de .NET.

Tareas de limpieza del DOM durante la eliminación de componentes

Para más información, consulta Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

Llamadas de interoperabilidad de JavaScript sin un circuito

Para obtener más información, consulta Interoperabilidad de JavaScript en ASP.NET Core Blazor (interoperabilidad de JS).

Recursos adicionales