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

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, vea 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), use las funciones de JS:

  • DotNet.invokeMethodAsync (Recomendado): asincrónico para las aplicaciones Blazor Server y Blazor WebAssembly.
  • DotNet.invokeMethod: solo sincrónico para las aplicaciones Blazor WebAssembly.

Pase 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 (solo Blazor WebAssembly) devuelve el resultado de la operación.

Importante

La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

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, vea la sección Llamada a métodos de clase genérica de .NET.

En el componente CallDotNetExample1 siguiente, 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.

Pages/CallDotNetExample1.razor:

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

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

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

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

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 de .NET ReturnArrayAsync del componente CallDotNetExample1 anterior y 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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) [ 1, 2, 3 ]

De forma predeterminada, el identificador de método de .NET para la llamada de JS es el nombre del método de .NET, pero puede 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 o DotNet.invokeMethod (solo Blazor WebAssembly), llame a DifferentMethodName para ejecutar el método de .NET ReturnArrayAsync:

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

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()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

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

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

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

  • Pase la instancia de .NET por referencia a JS encapsulando la instancia en un DotNetObjectReference y llamando a Create en ella.
  • Invoque un método de instancia de .NET desde JS mediante invokeMethodAsync o invokeMethod (solo Blazor WebAssembly) desde la clase DotNetObjectReference pasada. La instancia de .NET también se puede pasar como argumento al invocar otros métodos de .NET desde JS.
  • Deseche DotNetObjectReference.

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

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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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

Para el componente CallDotNetExample2 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.

Pages/CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

Para pasar argumentos al método de instancia:

  1. Agregue parámetros a la invocación del método de .NET. En el ejemplo siguiente, se pasa un nombre al método. Agregue 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.

  2. Proporcione la lista de parámetros al método de .NET.

    Pages/CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

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

En el componente CallDotNetExampleOneHelper siguiente, los botones Trigger JS function llaman a funciones JS estableciendo la propiedad JSonclick, no el atributo de directiva @onclick de Blazor.

Pages/CallDotNetExampleOneHelper.razor:

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

<PageTitle>Call .NET Example</PageTitle>

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

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

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

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

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

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

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

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

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

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>

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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.

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, consulte 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.

Para más información, consulte los siguientes artículos.

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.
  • Las aplicaciones Blazor WebAssembly admiten llamar a métodos de .NET sincrónicamente con invokeMethod. syncInterop recibe un valor booleano que indica si la inteoperabilidad de JS se está produciendo en una aplicación Blazor WebAssembly. 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 asincrónica invokeMethodAsync porque la inteoperabilidad de JS se ejecuta en una aplicación Blazor Server.
  • 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, consulte 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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.

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@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 };

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

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

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 una aplicación Blazor WebAssembly:

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 una aplicación Blazor Server, se evitan las llamadas sincrónicas con invokeMethod. La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

Salida típica de una aplicación Blazor Server:

JS: invokeMethodAsync:Update('string 34809')
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No 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;

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

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

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

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

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

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

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.

Cuando se selecciona el botón Trigger .NET instance method en el componente CallDotNetExample4 siguiente, se llama a JsInteropClasses3.CallHelloHelperGetHelloMessage con el valor de name.

Pages/CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

Pages/CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

La salida mostrada por el componente CallDotNetExample5 es Hello, Amy Pond! cuando el nombre Amy Pond se proporciona en el campo Name.

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

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

En el ejemplo anterior:

  • 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 {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.

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 los siguientes escenarios:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En una aplicación de Blazor Server, donde varios usuarios usan simultáneamente el mismo componente.

En el ejemplo siguiente:

  • El componente CallDotNetExample6 contiene varios componentes ListItem, que es un componente compartido en la carpeta Shared de la aplicación.
  • Cada componente ListItem consta de un mensaje y un botón.
  • Cuando se selecciona un botón de componente ListItem, el método UpdateMessage de ese objeto ListItem 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. BlazorSample es el nombre del ensamblado de la aplicación.

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

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

La siguiente función updateMessageCallerJS invoca el método de .NET UpdateMessageCaller. BlazorSample es el nombre del ensamblado de la aplicación.

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

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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

El componente ListItem 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 ListItem establece una instancia de MessageUpdateInvokeHelper con un elemento Action establecido en su método UpdateMessage.

Cuando se selecciona un botón InteropCall del componente ListItem, 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 ListItem. La clase DotNetObjectReference pasada se elimina en JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

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

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

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

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

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

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 primario CallDotNetExample6 incluye cuatro elementos de lista, y cada uno de ellos es una instancia del componente ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

En la imagen siguiente se muestra el componente primario CallDotNetExample6 representado después de seleccionar el segundo botón InteropCall :

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

Ejemplo del componente

Interoperabilidad JS sincrónica en aplicaciones Blazor WebAssembly

Esta sección solo se aplica a las aplicaciones .

De forma predeterminada, 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 asincrónicas de forma predeterminada para asegurarse de que los componentes son compatibles en ambos modelos Blazor de hospedaje, Blazor Server y Blazor WebAssembly. En Blazor Server, 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 sabe con certeza que la aplicación solo se ejecuta en Blazor WebAssembly, puede 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 hacer una llamada sincrónica desde JavaScript a .NET en las aplicaciones de Blazor WebAssembly, use DotNet.invokeMethod en lugar de DotNet.invokeMethodAsync.

Las llamadas sincrónicas funcionan si:

  • La aplicación se ejecuta en Blazor WebAssembly, no en Blazor Server.
  • 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

Cargue código de JavaScript (JS) mediante cualquiera de los enfoques descritos en el artículo de información general sobre interoperabilidad de JS:

Para obtener información sobre cómo aislar scripts en los módulos , consulte la sección Aislamiento de JavaScript en módulos de JavaScript.

Advertencia

No coloque 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 Blazor estándar (JS).

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 más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

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.

Proporcione una función sendByteArray de JS. Un botón del componente llama a la función y no devuelve un valor:

<script>
  window.sendByteArray = () => {
    const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
      0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
      0x20,0x43,0x61,0x70,0x74,0x69,0x61,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

Pages/CallDotNetExample7.razor:

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

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

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

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

Para obtener información sobre el uso de una matriz de bytes al llamar a JavaScript desde .NET, vea 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 Blazor de ASP.NET Core se describe la operación inversa, el streaming desde .NET a JavaScript mediante un objeto DotNetStreamReference.

En Cargas de archivos de Blazor en ASP.NET Core se describe cómo cargar un archivo en Blazor.

Límites de tamaño en las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a las aplicaciones Blazor Server. En Blazor WebAssembly, el marco no impone límites en cuanto al tamaño de las entradas y salidas de las llamadas de interoperabilidad de JavaScript (JS).

En Blazor Server, las llamadas de interoperabilidad de JS presentan un tamaño limitado por el tamaño máximo de los mensajes SignalR entrantes que se permite para los métodos del concentrador. Esto se aplica por medio de HubOptions.MaximumReceiveMessageSize (valor predeterminado: 32 KB). Los mensajes de JS a .NET SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente.

Cuando el registro de SignalR no está establecido en SignalR o Seguimiento, solo aparece un error relativo al tamaño del mensaje en la consola de herramientas de desarrollo del explorador:

Error: Conexión desconectada con el error "Error: El servidor ha devuelto un error al cerrarse: Conexión cerrada con un error.".

Cuando el registro del lado servidor de se establece en Depurar o Seguimiento, el registro del lado servidor inicia una excepción relativa a un error del tamaño del mensaje.

appsettings.Development.json:

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

Error:

System.IO.InvalidDataException: Se ha superado el tamaño máximo del mensaje de 32768B. El tamaño del mensaje se puede configurar en AddHubOptions.

Para aumentar el límite, establezca MaximumReceiveMessageSize en Program.cs. En el ejemplo siguiente se establece el tamaño máximo del mensaje de recepción en 64 KB (64*1024):

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

El aumento del límite de tamaño del mensaje entrante SignalR implica que se necesitan más recursos del servidor y lo expone a más riesgos por parte de un usuario malintencionado. Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Tenga en cuenta las instrucciones siguientes al desarrollar código que transfiera un gran volumen de datos entre JS y Blazor en aplicaciones Blazor Server:

  • Aproveche la compatibilidad de interoperabilidad de transmisión nativa para transferir datos mayores que el límite de tamaño del mensaje entrante SignalR:
  • Sugerencias generales:
    • No asigne objetos grandes en código JS y C#.
    • Libere la memoria consumida al completar o cancelar el proceso.
    • Aplique los requisitos adicionales siguientes por motivos de seguridad:
      • Declare el tamaño máximo del archivo o los datos que se pueden pasar.
      • Declare la tasa mínima de carga desde el cliente al servidor.
    • Después de que el servidor reciba los datos, los datos se pueden:
      • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
      • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.

Recursos adicionales

Para obtener información sobre cómo llamar a funciones de JS desde .NET, vea 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), use las funciones de JS:

  • DotNet.invokeMethodAsync (Recomendado): asincrónico para las aplicaciones Blazor Server y Blazor WebAssembly.
  • DotNet.invokeMethod: solo sincrónico para las aplicaciones Blazor WebAssembly.

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 (solo Blazor WebAssembly) devuelve el resultado de la operación.

Importante

La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

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, que se describen más adelante en este artículo.

En el componente CallDotNetExample1 siguiente, 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.

Pages/CallDotNetExample1.razor:

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

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

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

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

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 de .NET ReturnArrayAsync del componente CallDotNetExample1 anterior y registra el resultado en la consola de herramientas para desarrolladores web del explorador. BlazorSample es el nombre del ensamblado de la aplicación.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

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) [ 1, 2, 3 ]

De forma predeterminada, el identificador de método de .NET para la llamada de JS es el nombre del método de .NET, pero puede 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 o DotNet.invokeMethod (solo Blazor WebAssembly), llame a DifferentMethodName para ejecutar el método de .NET ReturnArrayAsync:

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

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()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

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

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

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

  • Pase la instancia de .NET por referencia a JS encapsulando la instancia en un DotNetObjectReference y llamando a Create en ella.
  • Invoque un método de instancia de .NET desde JS mediante invokeMethodAsync o invokeMethod (solo Blazor WebAssembly) desde la clase DotNetObjectReference pasada. La instancia de .NET también se puede pasar como argumento al invocar otros métodos de .NET desde JS.
  • Deseche DotNetObjectReference.

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

Ejemplos de instancias de componente

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

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

Para el componente CallDotNetExample2 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.

Pages/CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

Para pasar argumentos al método de instancia:

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

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    
  2. Proporcione la lista de parámetros al método de .NET.

    Pages/CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

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;

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 System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

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

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

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

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

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

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.

Cuando se selecciona el botón Trigger .NET instance method en el componente CallDotNetExample4 siguiente, se llama a JsInteropClasses3.CallHelloHelperGetHelloMessage con el valor de name.

Pages/CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

Pages/CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

La salida mostrada por el componente CallDotNetExample5 es Hello, Amy Pond! cuando el nombre Amy Pond se proporciona en el campo Name.

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

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

En el ejemplo anterior:

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

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 los siguientes escenarios:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En una aplicación de Blazor Server, donde varios usuarios usan simultáneamente el mismo componente.

En el ejemplo siguiente:

  • El componente CallDotNetExample6 contiene varios componentes ListItem, que es un componente compartido en la carpeta Shared de la aplicación.
  • Cada componente ListItem consta de un mensaje y un botón.
  • Cuando se selecciona un botón de componente ListItem, el método UpdateMessage de ese objeto ListItem 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. BlazorSample es el nombre del ensamblado de la aplicación.

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

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

La siguiente función updateMessageCallerJS invoca el método de .NET UpdateMessageCaller. BlazorSample es el nombre del ensamblado de la aplicación.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

El componente ListItem 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 ListItem establece una instancia de MessageUpdateInvokeHelper con un elemento Action establecido en su método UpdateMessage.

Cuando se selecciona un botón InteropCall del componente ListItem, 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 ListItem. La clase DotNetObjectReference pasada se elimina en JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

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

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

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

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

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

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 primario CallDotNetExample6 incluye cuatro elementos de lista, y cada uno de ellos es una instancia del componente ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

En la imagen siguiente se muestra el componente primario CallDotNetExample6 representado después de seleccionar el segundo botón InteropCall :

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

Ejemplo del componente

Ubicación de JavaScript

Cargue código de JavaScript (JS) mediante cualquiera de los enfoques descritos en el artículo de información general sobre interoperabilidad de JS:

Para obtener información sobre cómo aislar scripts en los módulos , consulte la sección Aislamiento de JavaScript en módulos de JavaScript.

Advertencia

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

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.

Límites de tamaño en las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a las aplicaciones Blazor Server. En Blazor WebAssembly, el marco no impone límites en cuanto al tamaño de las entradas y salidas de las llamadas de interoperabilidad de JavaScript (JS).

En Blazor Server, las llamadas de interoperabilidad de JS presentan un tamaño limitado por el tamaño máximo de los mensajes SignalR entrantes que se permite para los métodos del concentrador. Esto se aplica por medio de HubOptions.MaximumReceiveMessageSize (valor predeterminado: 32 KB). Los mensajes de JS a .NET SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente.

Cuando el registro de SignalR no está establecido en SignalR o Seguimiento, solo aparece un error relativo al tamaño del mensaje en la consola de herramientas de desarrollo del explorador:

Error: Conexión desconectada con el error "Error: El servidor ha devuelto un error al cerrarse: Conexión cerrada con un error.".

Cuando el registro del lado servidor de se establece en Depurar o Seguimiento, el registro del lado servidor inicia una excepción relativa a un error del tamaño del mensaje.

appsettings.Development.json:

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

Error:

System.IO.InvalidDataException: Se ha superado el tamaño máximo del mensaje de 32768B. El tamaño del mensaje se puede configurar en AddHubOptions.

Para aumentar el límite, establezca MaximumReceiveMessageSize en Startup.ConfigureServices:

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

El aumento del límite de tamaño del mensaje entrante SignalR implica que se necesitan más recursos del servidor y lo expone a más riesgos por parte de un usuario malintencionado. Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Una opción para leer cargas grandes consiste en enviar el contenido en fragmentos más pequeños y procesar la carga como Stream. Se puede usar al leer cargas grandes de JSON o si los datos están disponibles en JS como bytes sin formato. Para obtener un ejemplo en el que se muestra el envío de cargas binarias de gran tamaño en Blazor Server que usa técnicas similares a las del Blazor Server, vea la InputFile.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Tenga en cuenta las instrucciones siguientes al desarrollar código que transfiera un gran volumen de datos entre JS y Blazor en aplicaciones Blazor Server:

  • Segmente los datos en partes más pequeñas y envíe los segmentos de datos secuencialmente hasta que el servidor reciba todos los datos.
  • No asigne objetos grandes en código JS y C#.
  • No bloquee el subproceso de interfaz de usuario principal durante períodos largos al enviar o recibir datos.
  • Libere la memoria consumida al completar o cancelar el proceso.
  • Aplique los requisitos adicionales siguientes por motivos de seguridad:
    • Declare el tamaño máximo del archivo o los datos que se pueden pasar.
    • Declare la tasa mínima de carga desde el cliente al servidor.
  • Después de que el servidor reciba los datos, los datos se pueden:
    • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
    • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.

Aislamiento de JavaScript en módulos de JavaScript

Blazor permite el aislamiento de JavaScript (JS) en Blazor estándar (JS).

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 más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

Recursos adicionales

Para obtener información sobre cómo llamar a funciones de JS desde .NET, vea 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), use las funciones de JS:

  • DotNet.invokeMethodAsync (Recomendado): asincrónico para las aplicaciones Blazor Server y Blazor WebAssembly.
  • DotNet.invokeMethod: solo sincrónico para las aplicaciones Blazor WebAssembly.

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 (solo Blazor WebAssembly) devuelve el resultado de la operación.

Importante

La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

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, que se describen más adelante en este artículo.

En el componente CallDotNetExample1 siguiente, 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.

Pages/CallDotNetExample1.razor:

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

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

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

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

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 de .NET ReturnArrayAsync del componente CallDotNetExample1 anterior y registra el resultado en la consola de herramientas para desarrolladores web del explorador. BlazorSample es el nombre del ensamblado de la aplicación.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

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) [ 1, 2, 3 ]

De forma predeterminada, el identificador de método de .NET para la llamada de JS es el nombre del método de .NET, pero puede 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 o DotNet.invokeMethod (solo Blazor WebAssembly), llame a DifferentMethodName para ejecutar el método de .NET ReturnArrayAsync:

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

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()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

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

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

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

  • Pase la instancia de .NET por referencia a JS encapsulando la instancia en un DotNetObjectReference y llamando a Create en ella.
  • Invoque un método de instancia de .NET desde JS mediante invokeMethodAsync o invokeMethod (solo Blazor WebAssembly) desde la clase DotNetObjectReference pasada. La instancia de .NET también se puede pasar como argumento al invocar otros métodos de .NET desde JS.
  • Deseche DotNetObjectReference.

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

Ejemplos de instancias de componente

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

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

Para el componente CallDotNetExample2 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.

Pages/CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

Para pasar argumentos al método de instancia:

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

    <script>
      window.sayHello2 = (dotNetHelper, name) => {
        return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
      };
    </script>
    
  2. Proporcione la lista de parámetros al método de .NET.

    Pages/CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

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;

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 System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

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

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

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

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

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

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.

Cuando se selecciona el botón Trigger .NET instance method en el componente CallDotNetExample4 siguiente, se llama a JsInteropClasses3.CallHelloHelperGetHelloMessage con el valor de name.

Pages/CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

Pages/CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

La salida mostrada por el componente CallDotNetExample5 es Hello, Amy Pond! cuando el nombre Amy Pond se proporciona en el campo Name.

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

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

En el ejemplo anterior:

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

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 los siguientes escenarios:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En una aplicación de Blazor Server, donde varios usuarios usan simultáneamente el mismo componente.

En el ejemplo siguiente:

  • El componente CallDotNetExample6 contiene varios componentes ListItem, que es un componente compartido en la carpeta Shared de la aplicación.
  • Cada componente ListItem consta de un mensaje y un botón.
  • Cuando se selecciona un botón de componente ListItem, el método UpdateMessage de ese objeto ListItem 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. BlazorSample es el nombre del ensamblado de la aplicación.

MessageUpdateInvokeHelper.cs:

using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

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

La siguiente función updateMessageCallerJS invoca el método de .NET UpdateMessageCaller. BlazorSample es el nombre del ensamblado de la aplicación.

Dentro de la etiqueta </body> de cierre de wwwroot/index.html (Blazor WebAssembly) o Pages/_Host.cshtml (Blazor Server):

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

El componente ListItem 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 ListItem establece una instancia de MessageUpdateInvokeHelper con un elemento Action establecido en su método UpdateMessage.

Cuando se selecciona un botón InteropCall del componente ListItem, 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 ListItem. La clase DotNetObjectReference pasada se elimina en JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

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

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

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

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

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

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 primario CallDotNetExample6 incluye cuatro elementos de lista, y cada uno de ellos es una instancia del componente ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

En la imagen siguiente se muestra el componente primario CallDotNetExample6 representado después de seleccionar el segundo botón InteropCall :

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

Ejemplo del componente

Ubicación de JavaScript

Cargue código de JavaScript (JS) mediante cualquiera de los enfoques descritos en el artículo de información general sobre interoperabilidad de JS:

Advertencia

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

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.

Límites de tamaño en las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a las aplicaciones Blazor Server. En Blazor WebAssembly, el marco no impone límites en cuanto al tamaño de las entradas y salidas de las llamadas de interoperabilidad de JavaScript (JS).

En Blazor Server, las llamadas de interoperabilidad de JS presentan un tamaño limitado por el tamaño máximo de los mensajes SignalR entrantes que se permite para los métodos del concentrador. Esto se aplica por medio de HubOptions.MaximumReceiveMessageSize (valor predeterminado: 32 KB). Los mensajes de JS a .NET SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente.

Cuando el registro de SignalR no está establecido en SignalR o Seguimiento, solo aparece un error relativo al tamaño del mensaje en la consola de herramientas de desarrollo del explorador:

Error: Conexión desconectada con el error "Error: El servidor ha devuelto un error al cerrarse: Conexión cerrada con un error.".

Cuando el registro del lado servidor de se establece en Depurar o Seguimiento, el registro del lado servidor inicia una excepción relativa a un error del tamaño del mensaje.

appsettings.Development.json:

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

Error:

System.IO.InvalidDataException: Se ha superado el tamaño máximo del mensaje de 32768B. El tamaño del mensaje se puede configurar en AddHubOptions.

Para aumentar el límite, establezca MaximumReceiveMessageSize en Startup.ConfigureServices:

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

El aumento del límite de tamaño del mensaje entrante SignalR implica que se necesitan más recursos del servidor y lo expone a más riesgos por parte de un usuario malintencionado. Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Una opción para leer cargas grandes consiste en enviar el contenido en fragmentos más pequeños y procesar la carga como Stream. Se puede usar al leer cargas grandes de JSON o si los datos están disponibles en JS como bytes sin formato. Para obtener un ejemplo en el que se muestra el envío de cargas binarias de gran tamaño en Blazor Server que usa técnicas similares a las del Blazor Server, vea la InputFile.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Tenga en cuenta las instrucciones siguientes al desarrollar código que transfiera un gran volumen de datos entre JS y Blazor en aplicaciones Blazor Server:

  • Segmente los datos en partes más pequeñas y envíe los segmentos de datos secuencialmente hasta que el servidor reciba todos los datos.
  • No asigne objetos grandes en código JS y C#.
  • No bloquee el subproceso de interfaz de usuario principal durante períodos largos al enviar o recibir datos.
  • Libere la memoria consumida al completar o cancelar el proceso.
  • Aplique los requisitos adicionales siguientes por motivos de seguridad:
    • Declare el tamaño máximo del archivo o los datos que se pueden pasar.
    • Declare la tasa mínima de carga desde el cliente al servidor.
  • Después de que el servidor reciba los datos, los datos se pueden:
    • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
    • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.

Recursos adicionales

Para obtener información sobre cómo llamar a funciones de JS desde .NET, vea 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), use las funciones de JS:

  • DotNet.invokeMethodAsync (Recomendado): asincrónico para las aplicaciones Blazor Server y Blazor WebAssembly.
  • DotNet.invokeMethod: solo sincrónico para las aplicaciones Blazor WebAssembly.

Pase 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 (solo Blazor WebAssembly) devuelve el resultado de la operación.

Importante

La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

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, vea la sección Llamada a métodos de clase genérica de .NET.

En el componente CallDotNetExample1 siguiente, 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.

Pages/CallDotNetExample1.razor:

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

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

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

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

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 de .NET ReturnArrayAsync del componente CallDotNetExample1 anterior y 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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) [ 1, 2, 3 ]

De forma predeterminada, el identificador de método de .NET para la llamada de JS es el nombre del método de .NET, pero puede 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 o DotNet.invokeMethod (solo Blazor WebAssembly), llame a DifferentMethodName para ejecutar el método de .NET ReturnArrayAsync:

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

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()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

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

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

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

  • Pase la instancia de .NET por referencia a JS encapsulando la instancia en un DotNetObjectReference y llamando a Create en ella.
  • Invoque un método de instancia de .NET desde JS mediante invokeMethodAsync o invokeMethod (solo Blazor WebAssembly) desde la clase DotNetObjectReference pasada. La instancia de .NET también se puede pasar como argumento al invocar otros métodos de .NET desde JS.
  • Deseche DotNetObjectReference.

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

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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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

Para el componente CallDotNetExample2 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.

Pages/CallDotNetExample2.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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

Para pasar argumentos al método de instancia:

  1. Agregue parámetros a la invocación del método de .NET. En el ejemplo siguiente, se pasa un nombre al método. Agregue 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.

  2. Proporcione la lista de parámetros al método de .NET.

    Pages/CallDotNetExample3.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

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

En el componente CallDotNetExampleOneHelper siguiente, los botones Trigger JS function llaman a funciones JS estableciendo la propiedad JSonclick, no el atributo de directiva @onclick de Blazor.

Pages/CallDotNetExampleOneHelper.razor:

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

<PageTitle>Call .NET Example</PageTitle>

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

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

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

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

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

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

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

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

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

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>

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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.

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, consulte 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.

Para más información, consulte los siguientes artículos.

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.
  • Las aplicaciones Blazor WebAssembly admiten llamar a métodos de .NET sincrónicamente con invokeMethod. syncInterop recibe un valor booleano que indica si la inteoperabilidad de JS se está produciendo en una aplicación Blazor WebAssembly. 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 asincrónica invokeMethodAsync porque la inteoperabilidad de JS se ejecuta en una aplicación Blazor Server.
  • 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, consulte 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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.

Pages/GenericsExample.razor:

@page "/generics-example"
@using System.Runtime.InteropServices
@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 };

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

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

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 una aplicación Blazor WebAssembly:

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 una aplicación Blazor Server, se evitan las llamadas sincrónicas con invokeMethod. La función asincrónica (invokeMethodAsync) se prefiere a la versión sincrónica (invokeMethod) para admitir escenarios de Blazor Server.

Salida típica de una aplicación Blazor Server:

JS: invokeMethodAsync:Update('string 34809')
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No 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;

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

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

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

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

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

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

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.

Cuando se selecciona el botón Trigger .NET instance method en el componente CallDotNetExample4 siguiente, se llama a JsInteropClasses3.CallHelloHelperGetHelloMessage con el valor de name.

Pages/CallDotNetExample4.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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.

Pages/CallDotNetExample5.razor:

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

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

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

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

<p>
    @result
</p>

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

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

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

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

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.

La salida mostrada por el componente CallDotNetExample5 es Hello, Amy Pond! cuando el nombre Amy Pond se proporciona en el campo Name.

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

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

En el ejemplo anterior:

  • 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 {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.

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 los siguientes escenarios:

  • Cuando se representan varios componentes del mismo tipo en la misma página.
  • En una aplicación de Blazor Server, donde varios usuarios usan simultáneamente el mismo componente.

En el ejemplo siguiente:

  • El componente CallDotNetExample6 contiene varios componentes ListItem, que es un componente compartido en la carpeta Shared de la aplicación.
  • Cada componente ListItem consta de un mensaje y un botón.
  • Cuando se selecciona un botón de componente ListItem, el método UpdateMessage de ese objeto ListItem 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. BlazorSample es el nombre del ensamblado de la aplicación.

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

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

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

La siguiente función updateMessageCallerJS invoca el método de .NET UpdateMessageCaller. BlazorSample es el nombre del ensamblado de la aplicación.

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

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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

El componente ListItem 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 ListItem establece una instancia de MessageUpdateInvokeHelper con un elemento Action establecido en su método UpdateMessage.

Cuando se selecciona un botón InteropCall del componente ListItem, 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 ListItem. La clase DotNetObjectReference pasada se elimina en JS (dotNetHelper.dispose()).

Shared/ListItem.razor:

@inject IJSRuntime JS

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

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

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

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

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

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 primario CallDotNetExample6 incluye cuatro elementos de lista, y cada uno de ellos es una instancia del componente ListItem.

Pages/CallDotNetExample6.razor:

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

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

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

En la imagen siguiente se muestra el componente primario CallDotNetExample6 representado después de seleccionar el segundo botón InteropCall :

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

Ejemplo del componente

Interoperabilidad JS sincrónica en aplicaciones Blazor WebAssembly

Esta sección solo se aplica a las aplicaciones .

De forma predeterminada, 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 asincrónicas de forma predeterminada para asegurarse de que los componentes son compatibles en ambos modelos Blazor de hospedaje, Blazor Server y Blazor WebAssembly. En Blazor Server, 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 sabe con certeza que la aplicación solo se ejecuta en Blazor WebAssembly, puede 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 hacer una llamada sincrónica desde JavaScript a .NET en las aplicaciones de Blazor WebAssembly, use DotNet.invokeMethod en lugar de DotNet.invokeMethodAsync.

Las llamadas sincrónicas funcionan si:

  • La aplicación se ejecuta en Blazor WebAssembly, no en Blazor Server.
  • 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

Cargue código de JavaScript (JS) mediante cualquiera de los enfoques descritos en el artículo de información general sobre interoperabilidad de JS:

Para obtener información sobre cómo aislar scripts en los módulos , consulte la sección Aislamiento de JavaScript en módulos de JavaScript.

Advertencia

No coloque 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 Blazor estándar (JS).

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 más información, vea Llamada a funciones de JavaScript desde métodos de .NET en Blazor de ASP.NET Core.

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.

Proporcione una función sendByteArray de JS. Un botón del componente llama a la función y no devuelve un valor:

<script>
  window.sendByteArray = () => {
    const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
      0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
      0x20,0x43,0x61,0x70,0x74,0x69,0x61,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

Nota:

Para una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

Pages/CallDotNetExample7.razor:

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

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

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

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

Para obtener información sobre el uso de una matriz de bytes al llamar a JavaScript desde .NET, vea 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 Blazor de ASP.NET Core se describe la operación inversa, el streaming desde .NET a JavaScript mediante un objeto DotNetStreamReference.

En Cargas de archivos de Blazor en ASP.NET Core se describe cómo cargar un archivo en Blazor.

Límites de tamaño en las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a las aplicaciones Blazor Server. En Blazor WebAssembly, el marco no impone límites en cuanto al tamaño de las entradas y salidas de las llamadas de interoperabilidad de JavaScript (JS).

En Blazor Server, las llamadas de interoperabilidad de JS presentan un tamaño limitado por el tamaño máximo de los mensajes SignalR entrantes que se permite para los métodos del concentrador. Esto se aplica por medio de HubOptions.MaximumReceiveMessageSize (valor predeterminado: 32 KB). Los mensajes de JS a .NET SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente.

Cuando el registro de SignalR no está establecido en SignalR o Seguimiento, solo aparece un error relativo al tamaño del mensaje en la consola de herramientas de desarrollo del explorador:

Error: Conexión desconectada con el error "Error: El servidor ha devuelto un error al cerrarse: Conexión cerrada con un error.".

Cuando el registro del lado servidor de se establece en Depurar o Seguimiento, el registro del lado servidor inicia una excepción relativa a un error del tamaño del mensaje.

appsettings.Development.json:

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

Error:

System.IO.InvalidDataException: Se ha superado el tamaño máximo del mensaje de 32768B. El tamaño del mensaje se puede configurar en AddHubOptions.

Para aumentar el límite, establezca MaximumReceiveMessageSize en Program.cs. En el ejemplo siguiente se establece el tamaño máximo del mensaje de recepción en 64 KB (64*1024):

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

El aumento del límite de tamaño del mensaje entrante SignalR implica que se necesitan más recursos del servidor y lo expone a más riesgos por parte de un usuario malintencionado. Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Tenga en cuenta las instrucciones siguientes al desarrollar código que transfiera un gran volumen de datos entre JS y Blazor en aplicaciones Blazor Server:

  • Aproveche la compatibilidad de interoperabilidad de transmisión nativa para transferir datos mayores que el límite de tamaño del mensaje entrante SignalR:
  • Sugerencias generales:
    • No asigne objetos grandes en código JS y C#.
    • Libere la memoria consumida al completar o cancelar el proceso.
    • Aplique los requisitos adicionales siguientes por motivos de seguridad:
      • Declare el tamaño máximo del archivo o los datos que se pueden pasar.
      • Declare la tasa mínima de carga desde el cliente al servidor.
    • Después de que el servidor reciba los datos, los datos se pueden:
      • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
      • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.

Recursos adicionales