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

Nota

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

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

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

En este artículo, los términos servidor/ del lado servidor y cliente/del lado cliente se usan para distinguir ubicaciones donde se ejecuta el código de la aplicación:

  • Servidor/del lado servidor: representación interactiva del lado servidor (SSR interactivo) de una aplicación webBlazor.
  • Cliente/lado cliente
    • Representación del lado cliente (CSR) de una Blazor aplicación web.
    • Una aplicación Blazor WebAssembly.

Los ejemplos de componentes de documentación normalmente no configuran un modo de representación interactivo con una directiva @rendermode en el archivo de definición del componente (.razor):

  • En una aplicación web Blazor, el componente debe tener aplicado un modo de representación interactivo, ya sea en el archivo de definición del componente o heredado de un componente principal. Para obtener más información, vea Modos de representación de ASP.NET CoreBlazor.

  • En una aplicación Blazor WebAssembly independiente, los componentes funcionan de la forma en que se muestra y no necesitan un modo de representación porque los componentes siempre se ejecutan de forma interactiva en WebAssembly en una aplicación de Blazor WebAssembly.

Al usar los modos interactivos de representación automática o WebAssembly interactivos, el código de componente enviado al cliente se puede descompilar e inspeccionar. No coloque código privado, secretos de aplicación u otra información confidencial en los componentes representados en el cliente.

  • Servidor/lado servidor
    • Proyecto Server de una aplicación Blazor WebAssembly hospedada.
    • Una aplicación Blazor Server.
  • Cliente/lado cliente
    • El proyecto Client de una aplicación hospedada Blazor WebAssembly.
    • Una aplicación Blazor WebAssembly.

Para obtener instrucciones sobre el propósito y las ubicaciones de los archivos y carpetas, vea Estructura del proyecto de ASP.NET Core Blazor, que también describe la ubicación del script de inicio de Blazor y la ubicación del contenido de <head> y <body>.

La mejor forma de ejecutar el código de demostración consiste en descargar las BlazorSample_{PROJECT TYPE}aplicaciones de ejemploBlazor del repositorio de ejemplos de GitHub que coincida con la versión de .NET que quiere usar. No todos los ejemplos de documentación se encuentran actualmente en las aplicaciones de ejemplo, pero actualmente se está realizando un trabajo para trasladar la mayoría de los ejemplos de artículos de .NET 8 a las aplicaciones de ejemplo de .NET 8. Este trabajo se completará en el primer trimestre de 2024.

Invocar funciones JS

El marco Blazor registra IJSRuntime. Para llamar a JS desde .NET, inserte la abstracción IJSRuntime y llame a uno de los métodos siguientes:

Para los métodos de .NET anteriores que invocan funciones de JS:

  • El identificador de función (String) es relativo al ámbito global (window). Para llamar a window.someScope.someFunction, el identificador es someScope.someFunction. No es necesario registrar la función para poder llamarla.
  • Pase cualquier número de argumentos serializables de JSON en Object[] a una función de JS.
  • El token de cancelación (CancellationToken) propaga una notificación de que se deben cancelar las operaciones.
  • TimeSpan representa un límite de tiempo para una operación de JS.
  • El tipo de TValue devuelto también debe ser serializable con JSON. TValue debe coincidir con el tipo de .NET que mejor asignación tenga con el tipo de JSON devuelto.
  • Se devuelve un objeto JS Promise para los métodos InvokeAsync. InvokeAsync desencapsula el objeto Promise y devuelve el valor que el objeto Promise espera.

Para aplicaciones Blazor con pre representación activada, que es el valor por defecto para aplicaciones del lado del servidor, llamar a JS no es posible durante la pre representación. Para obtener más información, vea la sección Representación previa.

El siguiente ejemplo se basa en TextDecoder, un descodificador basado en JS. En el ejemplo se muestra cómo invocar una función de JS desde un método de C# que descarga un requisito del código de desarrollador a una API de JS existente. La función de JS acepta una matriz de bytes procedente de un método de C#, la descodifica y devuelve el texto al componente para que lo muestre.

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    return decodedArray;
  };
</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).

El componente siguiente:

  • Invoca la función de convertArrayJS con InvokeAsync al seleccionar un botón (Convert Array).
  • Después de llamar a la función de JS, la matriz que se ha pasado se convierte en una cadena. Dicha cadena se devuelve al componente para que la muestre (text).

CallJs1.razor:

@page "/call-js-1"
@inject IJSRuntime JS

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new MarkupString(await JS.InvokeAsync<string>("convertArray", 
            quoteArray));
    }
}

API de JavaScript restringida a los gestos del usuario

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

Algunas API de JavaScript (JS) del explorador solo se pueden ejecutar en el contexto de un gesto del usuario, como el uso de Fullscreen API (documentación de MDN). Estas API no pueden invocarse a través del mecanismo de interoperabilidad JS en componentes del lado del servidor porque la administración de eventos de interfaz de usuario se realiza de forma asíncrona y, por lo general, ya no en el contexto del gesto del usuario. La aplicación debe controlar completamente el evento de interfaz de usuario en JavaScript, por lo que debe usar onclick en lugar del atributo de directiva @onclickde Blazor.

Invocación de funciones de JavaScript sin leer un valor devuelto (InvokeVoidAsync)

Utilice InvokeVoidAsync si:

  • .NET no es necesario para leer el resultado de una llamada de JavaScript (JS).
  • Las funciones de JS devuelven void(0)/void 0 o undefined.

Proporcione una función displayTickerAlert1 de JS. Se llama a la función con InvokeVoidAsync y no se devuelve un valor:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</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).

Ejemplo (InvokeVoidAsync) de componente (.razor)

TickerChanged llama al método handleTickerChanged1 en el siguiente componente.

CallJs2.razor:

@page "/call-js-2"
@inject IJSRuntime JS

<PageTitle>Call JS 2</PageTitle>

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Ejemplo (InvokeVoidAsync) de clase (.cs)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged llama al método handleTickerChanged1 en el siguiente componente.

CallJs3.razor:

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

<PageTitle>Call JS 3</PageTitle>

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

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

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses1(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

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

Invocación de funciones de JavaScript y lectura de un valor devuelto (InvokeAsync)

Use InvokeAsync cuando .NET debe leer el resultado de una llamada de JavaScript (JS).

Proporcione una función displayTickerAlert2 de JS. En el ejemplo siguiente se devuelve una cadena para que la muestre el llamador:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</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).

Ejemplo (InvokeAsync) de componente (.razor)

TickerChanged llama al método handleTickerChanged2 y muestra la cadena devuelta en el siguiente componente.

CallJs4.razor:

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

<PageTitle>Call JS 4</PageTitle>

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Ejemplo (InvokeAsync) de clase (.cs)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged llama al método handleTickerChanged2 y muestra la cadena devuelta en el siguiente componente.

CallJs5.razor:

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

<PageTitle>Call JS 5</PageTitle>

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

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

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

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

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

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

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

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

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

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

CallJsExample5.razor:

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

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses2(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

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

Escenarios de generación de contenido dinámico

Para generar contenido dinámico con BuildRenderTree, use el atributo [Inject]:

[Inject]
IJSRuntime JS { get; set; }

Representación previa

Esta sección se dirige a las aplicaciones del lado servidor que ofrecen una representación previa de los componentes de Razor. La representación previa se describe en Representación previa de componentes Razor de ASP.NET Core.

Nota:

La navegación interna para el enrutamiento interactivo en Web Apps Blazor no implica solicitar contenido de página nuevo desde el servidor. Por lo tanto, la representación previa no se produce para las solicitudes de página internas. Si la aplicación adopta el enrutamiento interactivo, realice una recarga de página completa para los ejemplos de componentes que muestran el comportamiento de representación previa. Para más información, consulte Representación previa de componentes Razor de ASP.NET Core .

Esta sección se aplica a las aplicaciones del lado del servidor y a las aplicaciones Blazor WebAssembly alojadas que pre representan componentes Razor. La representación previa se describe en Integración y representación previa de componentes Razor de ASP.NET Core.

Durante la representación previa de una aplicación, no es posible realizar ciertas acciones, como llamar a JavaScript (JS).

En el ejemplo siguiente, se llama a la función setElementText1 con JSRuntimeExtensions.InvokeVoidAsync y no devuelve un valor.

Nota:

Para obtener instrucciones sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad JS).

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Advertencia

En el ejemplo anterior se modifica el DOM directamente solo con fines de demostración. No se recomienda modificar directamente el DOM con JS en la mayoría de los escenarios, ya que JS puede interferir con el seguimiento de cambios de Blazor. Para más información, vea Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

Durante el proceso de representación previa en el servidor no se llama al evento de ciclo de vida OnAfterRender{Async}. Invalide el método OnAfterRender{Async} para retrasar las llamadas de interoperabilidad de JS hasta que el componente se represente y sea interactivo en el cliente después de la representación previa.

PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Nota:

En el ejemplo anterior se contamina el cliente con funciones globales. Para obtener un mejor enfoque en las aplicaciones de producción, consulte Aislamiento de JavaScript en módulos de JavaScript.

Ejemplo:

export setElementText1 = (element, text) => element.innerText = text;

En el componente siguiente se muestra cómo usar la interoperabilidad de JS como parte de la lógica de inicialización de un componente de una manera compatible con la representación previa. El componente muestra que es posible desencadenar una actualización de representación desde dentro de OnAfterRenderAsync. El desarrollador debe tener cuidado y evitar la creación de un bucle infinito en este escenario.

En el ejemplo siguiente, se llama a la función setElementText2 con IJSRuntime.InvokeAsync y devuelve un valor.

Nota:

Para obtener instrucciones sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad JS).

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Advertencia

En el ejemplo anterior se modifica el DOM directamente solo con fines de demostración. No se recomienda modificar directamente el DOM con JS en la mayoría de los escenarios, ya que JS puede interferir con el seguimiento de cambios de Blazor. Para más información, vea Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

Cuando se llama a JSRuntime.InvokeAsync, ElementReference solo se utiliza en OnAfterRenderAsync y no en ningún otro método de ciclo de vida anterior porque no existe ningún elemento de JS hasta después de representar el componente.

Se llama a StateHasChanged para volver a representar el componente con el nuevo estado obtenido de la llamada de interoperabilidad de JS (para obtener más información, vea Representación de componentes de Razor de ASP.NET Core). El código no crea un bucle infinito porque solo se llama a StateHasChanged cuando null es data.

PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call: 
    <strong id="val-set-by-interop" @ref="divElement"></strong>
</p>



@code {
    private string? infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Nota:

En el ejemplo anterior se contamina el cliente con funciones globales. Para obtener un mejor enfoque en las aplicaciones de producción, consulte Aislamiento de JavaScript en módulos de JavaScript.

Ejemplo:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

JS Interoperabilidad sincrónica en componentes del lado cliente

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

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 asíncronas por defecto para garantizar que los componentes sean compatibles entre los modos de renderizado del lado del servidor y del lado del cliente. En JS 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 JS 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 realizar una llamada sincrónica de .NET a JavaScript en un componente del lado cliente, convierta IJSRuntime en IJSInProcessRuntime para realizar la llamada de interoperabilidad de JS :

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Al trabajar con componentes del lado del cliente de IJSObjectReferenceASP.NET Core 5.0 o posterior, puede usarIJSInProcessObjectReference sincrónicamente en su lugar. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable y debe eliminarse para la recolección de elementos no utilizados para evitar una fuga de memoria, como se muestra en el ejemplo siguiente:

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

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

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 JavaScript (JS):

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

Advertencia

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

Advertencia

No 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 módulos de JavaScript estándar (especificación de ECMAScript). La carga de módulos de JavaScript funciona de la misma manera en Blazor que en otros tipos de aplicaciones web, y puede personalizar cómo se definen los módulos en su aplicación. Para obtener una guía sobre cómo usar módulos de JavaScript, consulte Documentación web de MDN: módulos de JavaScript.

El aislamiento de JS proporciona las siguientes ventajas:

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

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

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

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

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

Por ejemplo, el siguiente módulo de JS exporta una función de JS para mostrar un mensaje de la ventana del explorador. Coloque el código de JS siguiente en un archivo de JS externo.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Agregue el módulo de JS anterior a una aplicación o biblioteca de clases como un recurso web estático en la carpeta wwwroot y, después, importe dicho módulo al código .NET llamando a InvokeAsync en la instancia IJSRuntime.

IJSRuntime importa el módulo como un elemento IJSObjectReference, que es una referencia a un objeto de JS hecha desde código .NET. Use IJSObjectReference para invocar funciones de JS exportadas desde el módulo.

CallJs6.razor:

@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 6</PageTitle>

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference module;
    private string result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string> Prompt(string message)
    {
        return await module.InvokeAsync<string>("showPrompt", message);
    }

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

En el ejemplo anterior:

  • Por convención, el identificador de import es un identificador especial que se usa específicamente para importar un módulo de JS.
  • Especifique el archivo JS externo del módulo mediante su ruta de acceso de recurso web estático estable: ./{SCRIPT PATH AND FILE NAME (.js)}, donde:
    • El segmento de trazado del directorio actual (./) es necesario para crear la ruta de recurso estático correcta para el archivo JS.
    • El marcador de posición {SCRIPT PATH AND FILE NAME (.js)} es la ruta de acceso y el nombre de archivo en wwwroot.
  • Elimina el elemento IJSObjectReference para la recolección de elementos no utilizados en IAsyncDisposable.DisposeAsync.

La importación dinámica de un módulo requiere una solicitud de red, por lo que solo se puede lograr de forma asincrónica llamando a InvokeAsync.

IJSInProcessObjectReference representa una referencia a un objeto JS cuyas funciones pueden invocarse de forma sincrónica en componentes del lado del cliente. Para más información, consulte la sección Interoperabilidad síncronaJS en componentes del lado del cliente.

Nota:

Cuando una biblioteca de clases de Razor proporciona el archivo externo JS, especifique el archivo JS del módulo mediante su ruta de acceso estática estable del recurso web: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • El segmento de trazado del directorio actual (./) es necesario para crear la ruta de recurso estático correcta para el archivo JS.
  • El marcador de posición {PACKAGE ID} es el id. de paquete de la biblioteca. El id. de paquete tiene como valor predeterminado el nombre de ensamblado del proyecto si <PackageId> no se especifica en el archivo del proyecto. En el ejemplo siguiente, el nombre del ensamblado de la biblioteca es ComponentLibrary y el archivo de proyecto de la biblioteca no especifica <PackageId>.
  • El marcador de posición {SCRIPT PATH AND FILE NAME (.js)} es la ruta de acceso y el nombre de archivo en wwwroot. En el ejemplo siguiente, el archivo JS externo (script.js) se coloca en la carpeta wwwroot de la biblioteca de clases.
  • module es un valor nullable privado IJSObjectReference de la clase de componente (private IJSObjectReference? module;).
module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Para obtener más información, vea Consumo de componentes Razor de ASP.NET Core a partir de una biblioteca de clases de Razor (RCL).

En la documentación de Blazor, los ejemplos usan la extensión de archivo .js para los archivos de módulo, no la extensión de archivo .mjs más reciente (RFC 9239). Nuestra documentación sigue usando la extensión de archivo .js por los mismos motivos por los que la documentación de Mozilla Foundation sigue usando la extensión de archivo .js. Para obtener más información, consulte la sección sobre .mjs frente a .js (documentación de MDN).

Captura de referencias a elementos

Algunos escenarios de interoperabilidad de JavaScript (JS) requieren referencias a elementos HTML. Por ejemplo, una biblioteca de interfaz de usuario puede requerir una referencia de elemento para inicializarse, o puede que necesite llamar a API de tipo comando en un elemento, como click o play.

Use el siguiente método para capturar referencias a elementos HTML en un componente:

  • Agregue un atributo @ref al elemento HTML.
  • Defina un campo de tipo ElementReference cuyo nombre coincida con el valor del atributo @ref.

En el siguiente ejemplo se muestra la captura de una referencia al elemento username<input>:

<input @ref="username" ... />

@code {
    private ElementReference username;
}

Advertencia

Use únicamente una referencia de elemento para mutar el contenido de un elemento vacío que no interactúa con Blazor. Este escenario es útil cuando una API de terceros proporciona contenido al elemento. Dado que Blazor no interactúa con el elemento, no existe ninguna posibilidad de que se produzca un conflicto entre la representación de Blazor del elemento y el DOM.

En el siguiente ejemplo, es peligroso mutar el contenido de la lista desordenada (ul) con MyList mediante JS porque Blazor interactúa con el DOM para rellenar los elementos de lista de este elemento (<li>) del objeto Todos:

<ul @ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Se admite el uso de la referencia de elemento MyList para leer simplemente contenido DOM o desencadenar un evento.

Si la interoperabilidad de JSmuta el contenido del elemento MyList y Blazor intenta aplicar diferencias con ese elemento, las diferencias no coincidirán con el DOM. Modificar el contenido de la lista mediante la interoperabilidad de JS con la referencia de elemento MyListno se admite.

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

ElementReference se pasa al código de JS por medio de la interoperabilidad de JS. El código de JS recibe una instancia de HTMLElement que puede usar con las API de DOM habituales. Por ejemplo, el siguiente código define un método de extensión de .NET (TriggerClickEvent) que permite enviar un clic del mouse a un elemento.

La función de JSclickElement crea un evento click en el elemento HTML pasado (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Para llamar a una función de JS que no devuelve un valor, use JSRuntimeExtensions.InvokeVoidAsync. El siguiente código desencadena un evento click de cliente al llamar a la función de JS anterior con el elemento ElementReference capturado:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Para usar un método de extensión, cree un método de extensión estático que reciba la instancia de IJSRuntime:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Se llama al método clickElement directamente en el objeto. En el siguiente ejemplo se da por hecho que el método TriggerClickEvent está disponible en el espacio de nombres JsInteropClasses:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Importante

La variable exampleButton solo se rellena después de que el componente se represente. Si se pasa un elemento ElementReference sin rellenar al código de JS, el código de JS recibe un valor null. Para manipular referencias de elemento una vez finalizada la representación del componente, use los métodos de ciclo de vida del componente OnAfterRenderAsync o OnAfterRender.

Cuando se trabaje con tipos genéricos y se devuelva un valor, use ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

El marcador de posición {JAVASCRIPT FUNCTION} es el identificador de la función JS.

Se llama al método GenericMethod directamente en el objeto con un tipo. En el siguiente ejemplo se da por hecho que el método GenericMethod está disponible en el espacio de nombres JsInteropClasses:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string? returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}
@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string? returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}
@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

Referencia a elementos en componentes

ElementReference no se puede pasar entre componentes porque:

  • Solo se garantiza que la instancia vaya a existir después de que se represente el componente, lo que es durante o después de la ejecución del método OnAfterRender/OnAfterRenderAsync del componente.
  • Un ElementReference es un struct, que no se puede pasar como parámetro de componente de .

Para que un componente primario haga que una referencia de elemento esté disponible para otros componentes, el componente primario puede:

  • Permitir que los componentes secundarios registren devoluciones de llamada.
  • Invoca las devoluciones de llamada registradas durante el evento OnAfterRender con la referencia de elemento que se ha pasado. Este método permite de forma indirecta que los componentes secundarios interactúen con la referencia del elemento primario.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</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).

CallJs7.razor (componente primario):

@page "/call-js-7"

<PageTitle>Call JS 7</PageTitle>

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (componente primario):

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

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (componente primario):

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

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (componente primario):

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

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (componente primario):

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

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJs7.razor.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJs7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private readonly List<IObserver<ElementReference>> subscriptions = [];
    private ElementReference title;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnNext(title);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    public void Dispose()
    {
        disposing = true;

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnCompleted();
            }
            catch (Exception)
            {
            }
        }

        subscriptions.Clear();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }

    public IDisposable Subscribe(IObserver<ElementReference> observer)
    {
        if (disposing)
        {
            throw new InvalidOperationException("Parent being disposed");
        }

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

    private class Subscription(IObserver<ElementReference> observer,
        CallJs7 self) : IDisposable
    {
        public IObserver<ElementReference> Observer { get; } = observer;
        public CallJs7 Self { get; } = self;

        public void Dispose() => Self.subscriptions.Remove(Observer);
    }
}

CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJsExample7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private IList<IObserver<ElementReference>> subscriptions = 
        new List<IObserver<ElementReference>>();
    private ElementReference title;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnNext(title);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    public void Dispose()
    {
        disposing = true;

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnCompleted();
            }
            catch (Exception)
            {
            }
        }

        subscriptions.Clear();
    }

    public IDisposable Subscribe(IObserver<ElementReference> observer)
    {
        if (disposing)
        {
            throw new InvalidOperationException("Parent being disposed");
        }

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

    private class Subscription : IDisposable
    {
        public Subscription(IObserver<ElementReference> observer, 
            CallJsExample7 self)
        {
            Observer = observer;
            Self = self;
        }

        public IObserver<ElementReference> Observer { get; }
        public CallJsExample7 Self { get; }

        public void Dispose()
        {
            Self.subscriptions.Remove(Observer);
        }
    }
}

CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJsExample7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private IList<IObserver<ElementReference>> subscriptions = 
        new List<IObserver<ElementReference>>();
    private ElementReference title;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnNext(title);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    public void Dispose()
    {
        disposing = true;

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnCompleted();
            }
            catch (Exception)
            {
            }
        }

        subscriptions.Clear();
    }

    public IDisposable Subscribe(IObserver<ElementReference> observer)
    {
        if (disposing)
        {
            throw new InvalidOperationException("Parent being disposed");
        }

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

    private class Subscription : IDisposable
    {
        public Subscription(IObserver<ElementReference> observer, 
            CallJsExample7 self)
        {
            Observer = observer;
            Self = self;
        }

        public IObserver<ElementReference> Observer { get; }
        public CallJsExample7 Self { get; }

        public void Dispose()
        {
            Self.subscriptions.Remove(Observer);
        }
    }
}

CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool disposing;
        private IList<IObserver<ElementReference>> subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnNext(title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            disposing = true;

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public CallJsExample7 Self { get; }

            public void Dispose()
            {
                Self.subscriptions.Remove(Observer);
            }
        }
    }
}

CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool disposing;
        private IList<IObserver<ElementReference>> subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnNext(title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            disposing = true;

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public CallJsExample7 Self { get; }

            public void Dispose()
            {
                Self.subscriptions.Remove(Observer);
            }
        }
    }
}

En el ejemplo anterior, el espacio de nombres de la aplicación es BlazorSample. Si prueba el código de forma local, actualice el espacio de nombres.

SurveyPrompt.razor (componente secundario):

<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}

SurveyPrompt.razor.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Components;

public partial class SurveyPrompt : 
    ComponentBase, IObserver<ElementReference>, IDisposable
{
    private IDisposable? subscription = null;

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = Parent?.Subscribe(this);
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void  OnNext(ElementReference value)
    {
        _ = (JS?.InvokeAsync<object>(
            "setElementClass", [value, "red"]));
    }

    public void Dispose()
    {
        subscription?.Dispose();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared;

public partial class SurveyPrompt : 
    ComponentBase, IObserver<ElementReference>, IDisposable
{
    private IDisposable? subscription = null;

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = 
            Parent is not null ? Parent.Subscribe(this) : null;
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void OnNext(ElementReference value)
    {
        JS.InvokeAsync<object>(
            "setElementClass", new object[] { value, "red" });
    }

    public void Dispose()
    {
        subscription?.Dispose();
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared;

public partial class SurveyPrompt : 
    ComponentBase, IObserver<ElementReference>, IDisposable
{
    private IDisposable? subscription = null;

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = 
            Parent is not null ? Parent.Subscribe(this) : null;
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void OnNext(ElementReference value)
    {
        JS.InvokeAsync<object>(
            "setElementClass", new object[] { value, "red" });
    }

    public void Dispose()
    {
        subscription?.Dispose();
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        [Inject]
        public IJSRuntime JS {get; set;}

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            subscription?.Dispose();
        }
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        [Inject]
        public IJSRuntime JS {get; set;}

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

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

En el ejemplo anterior, el espacio de nombres de la aplicación es BlazorSample con componentes compartidos en la carpeta Shared. Si prueba el código de forma local, actualice el espacio de nombres.

Protección de las llamadas de interoperabilidad de JavaScript

Esta sección solo se aplica a los componentes del Servidor interactivo., pero los componentes del lado del cliente también pueden establecer tiempos de espera de interoperabilidad JS si las condiciones lo justifican.

En aplicaciones del lado del servidor con interactividad del servidor, la interoperabilidad de JavaScript (JS) puede fallar debido a errores de red y debe tratarse como no fiable. De forma predeterminada, las aplicaciones Blazor usan un tiempo de espera de un minuto para las llamadas de interoperabilidad de JS. Si una aplicación puede tolerar un tiempo de espera más restrictivo, establézcalo recurriendo a uno de los siguientes métodos.

Establezca un tiempo de espera global en Program.cs con CircuitOptions.JSInteropDefaultCallTimeout:

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents(options => 
        options.JSInteropDefaultCallTimeout = {TIMEOUT});
builder.Services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Establezca un tiempo de espera global en el método Startup.ConfigureServices de Startup.cs con CircuitOptions.JSInteropDefaultCallTimeout:

services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

El marcador de posición {TIMEOUT} es un TimeSpan (por ejemplo, TimeSpan.FromSeconds(80)).

Establezca un tiempo de espera por cada invocación en el código de componente. El tiempo de espera especificado invalida el tiempo de espera global establecido por JSInteropDefaultCallTimeout:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

En el ejemplo anterior:

  • El marcador de posición {TIMEOUT} es un TimeSpan (por ejemplo, TimeSpan.FromSeconds(80)).
  • El marcador de posición {ID} es el identificador de la función que se va a invocar. Por ejemplo, el valor someScope.someFunction invoca la función window.someScope.someFunction.

Aunque una causa común de fallos de interoperabilidad JS son los fallos de red con componentes del lado del servidor, se pueden establecer tiempos de espera por invocación para llamadas de interoperabilidad JS para componentes del lado del cliente. Aunque no exista ningún circuito SignalR para un componente del lado del cliente, las llamadas de interoperabilidad JS podrían fallar por otras razones que se apliquen.

Para más información sobre el agotamiento de recursos, consulte Guía de mitigación de amenazas para Blazor la representación interactiva en el lado del servidor 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.

Bibliotecas de JavaScript que representan la interfaz de usuario

En ocasiones, es posible que quiera usar bibliotecas de JavaScript (JS) que generen elementos de la interfaz de usuario visibles en el DOM del explorador. A primera vista, esto podría parecer difícil porque el sistema de comparación de Blazor se basa en tener control sobre el árbol de elementos DOM y encuentra errores si algún código externo muta el árbol DOM e invalida su mecanismo para aplicar las comparaciones. No se trata de una limitación específica de Blazor. El mismo desafío se produce con cualquier marco de interfaz de usuario basado en comparaciones.

Afortunadamente, es sencillo insertar una interfaz de usuario generada externamente dentro de una interfaz de usuario de componentes de Razor de forma confiable. La técnica recomendada consiste en hacer que el código del componente (archivo .razor) produzca un elemento vacío. En lo que se refiere al sistema de comparación de Blazor, el elemento siempre está vacío, por lo que el representador no recorre en el elemento y no interferirá con sus contenidos. Esto hace que sea seguro rellenar el elemento con contenido arbitrario administrado externamente.

En el siguiente ejemplo se muestra el concepto. Dentro de la instrucción if cuando firstRender es true, interactúe con unmanagedElement fuera de Blazor mediante la interoperabilidad de JS. Por ejemplo, llame a una biblioteca de JS externa para rellenar el elemento. Blazor no hará nada con el contenido del elemento hasta que se quite este componente. Cuando se quite el componente, también se quitará el subárbol DOM completo del componente.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Considere el ejemplo siguiente que representa un mapa interactivo mediante las API de código abierto de Mapbox.

El módulo de JS siguiente se coloca en la aplicación o está disponible en una biblioteca de clases de Razor.

Nota

Para crear el mapa de Mapbox, obtenga un token de acceso de Inicio de sesión de Mapbox y proporciónelo donde {ACCESS TOKEN} aparece en el código siguiente.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Para generar el estilo correcto, agregue la siguiente etiqueta de hoja de estilos a la página HTML del host.

Agregue el elemento <link> siguiente al marcado del elemento <head> (ubicación del contenido de <head>):

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

CallJs8.razor:

@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 8</PageTitle>

<HeadContent>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
        rel="stylesheet" />
</HeadContent>

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

En el ejemplo anterior se genera una interfaz de usuario de mapa interactiva. El usuario:

  • Puede arrastrar para desplazarse o hacer zoom.
  • Puede hacer clic en los botones para saltar a ubicaciones predefinidas.

Mapbox street map of Tokyo, Japan with buttons to select Bristol, United Kingdom and Tokyo, Japan

En el ejemplo anterior:

  • En cuanto a Blazor, <div> con @ref="mapElement" se deja vacío. El script mapbox-gl.js puede rellenar de forma segura el elemento y modificar su contenido. Utilice esta técnica con cualquier biblioteca de JS que represente la interfaz de usuario. Puede insertar componentes de un marco de JS SPA de terceros dentro de los componentes de Razor, siempre y cuando no intenten modificar otras partes de la página. No es seguro que el código JS externo modifique los elementos que Blazor no considere vacíos.
  • Al utilizar este enfoque, tenga en cuenta las reglas sobre cómo Blazor conserva o destruye los elementos DOM. El componente controla de forma segura los eventos de clic de botón y actualiza la instancia del mapa existente porque, de forma predeterminada, los elementos DOM se conservan siempre que sea posible. Si estuviera representando una lista de elementos de mapa desde dentro de un bucle @foreach, querría usar @key para garantizar la preservación de las instancias del componente. De lo contrario, los cambios en los datos de la lista podrían hacer que las instancias del componente conservaran el estado de las instancias anteriores de forma no deseada. Para más información, vea cómo usar el atributo @key de directiva para conservar la relación entre elementos, componentes y objetos de modelo.
  • El ejemplo encapsula la lógica de JS y las dependencias dentro de un módulo ES6 y carga el módulo dinámicamente mediante el identificador import. Para más información, consulte Aislamiento de JavaScript en módulos de JavaScript.

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

Proporcione una función receiveByteArray de JS. Se llama a la función con InvokeVoidAsync y no se devuelve un valor:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return 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).

CallJs9.razor:

@page "/call-js-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</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 {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 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 };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

CallJsExample9.razor:

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</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 {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 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 };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

Para obtener información sobre el uso de una matriz de bytes al llamar a .NET desde JavaScript, vea Llamada a métodos de .NET desde funciones de JavaScript en ASP.NET Core Blazor.

Transmisión de .NET a JavaScript

Blazor admite la transmisión de datos directamente desde .NET a JavaScript. Los flujos se crean mediante el objeto DotNetStreamReference.

DotNetStreamReference representa una secuencia de .NET y usa los parámetros siguientes:

  • stream: la secuencia enviada a JavaScript.
  • leaveOpen: determina si la secuencia sigue abierta después de la transmisión. Si no se proporciona un valor, leaveOpen tiene como valor predeterminado false.

En JavaScript, use un búfer de matriz o una secuencia legible para recibir los datos:

  • Mediante un objeto ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Mediante un objeto ReadableStream:

    async function streamToJavaScript(streamRef) {
      const stream = await streamRef.stream();
    }
    

En código de C#:

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

En el ejemplo anterior:

  • El marcador de posición {STREAM} representa el objeto Stream enviado a JavaScript.
  • JS es una instancia de IJSRuntime insertada.

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

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

Detección de excepciones de JavaScript

Para detectar excepciones JS, encapsule la interoperabilidad de JS en un bloque try-catch y detecte una excepción JSException.

En el siguiente ejemplo, la función nonFunctionJS no existe. Cuando no se encuentra la función, el objeto JSExceptionqueda atrapado con una excepción Message que indica el siguiente error:

Could not find 'nonFunction' ('nonFunction' was undefined).

CallJs11.razor:

@page "/call-js-11"
@inject IJSRuntime JS

<PageTitle>Call JS 11</PageTitle>

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Anulación de una función de JavaScript de ejecución larga

Use JSAbortController con CancellationTokenSource en el componente para anular una función de JavaScript de ejecución larga desde código de C#.

La clase JSHelpers siguiente contiene una función simulada de ejecución larga, longRunningFn, que se cuenta continuamente hasta que AbortController.signal indica que se ha llamado a AbortController.abort. La función sleep tiene fines de demostración para simular la ejecución lenta de la función de ejecución larga y no estaría presente en el código de producción. Cuando un componente llama a stopFn, se indica a longRunningFn que se anule a través de la comprobación condicional del bucle while en AbortSignal.aborted.

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</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).

El componente siguiente:

CallJs12.razor:

@page "/call-js-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

CallJsExample12.razor:

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

La consola de herramientas del desarrollador de un explorador indica la ejecución de la función de duración larga JS después de seleccionar el botón Start Task y cuando se anula la función después de seleccionar el botón Cancel Task:

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

Interoperabilidad [JSImport]/[JSExport] de JavaScript

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

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

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

Interoperabilidad de JavaScript deserializada

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

La interoperabilidad deserializada mediante la interfaz IJSUnmarshalledRuntime está obsoleta y debe reemplazarse por la interoperabilidad [JSImport]/[JSExport] de JavaScript.

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

Interoperabilidad de JavaScript deserializada

Los componentes de Blazor WebAssembly pueden experimentar un rendimiento deficiente cuando se serializan objetos .NET para la interoperabilidad de JavaScript (JS) y se cumple cualquiera de las siguientes condiciones:

  • Un gran volumen de objetos .NET se serializan rápidamente. Por ejemplo, se puede producir un rendimiento deficiente cuando las llamadas de interoperabilidad de JS se realizan en función del movimiento de un dispositivo de entrada, como girar una rueda del mouse.
  • Los objetos .NET grandes o muchos objetos .NET deben serializarse para la interoperabilidad de JS. Por ejemplo, se puede producir un rendimiento deficiente cuando las llamadas de interoperabilidad de JS requieren la serialización de docenas de archivos.

IJSUnmarshalledObjectReference representa una referencia a un objeto de JS cuyas funciones se pueden invocar sin la sobrecarga de serializar los datos de .NET.

En el ejemplo siguiente:

  • Un struct que contiene una cadena y un entero se pasa sin serializar a JS.
  • Las funciones de JS procesan los datos y devuelven un valor booleano o una cadena al autor de la llamada.
  • Una cadena de JS no se puede convertir directamente en un objeto string de .NET. La función unmarshalledFunctionReturnString llama a BINDING.js_string_to_mono_string para administrar la conversión de una cadena de JS.

Nota

Los ejemplos siguientes no son casos de uso típicos para este escenario porque el struct pasado a JS no da lugar a un rendimiento deficiente del componente. En el ejemplo se usa un objeto pequeño simplemente para mostrar los conceptos para pasar datos de .NET no serializados.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Nota:

Para obtener instrucciones sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad JS).

Advertencia

El nombre, el comportamiento y la existencia de la función js_string_to_mono_string están sujetos a cambios en una versión futura de .NET. Por ejemplo:

  • Es probable que se cambie el nombre de la función.
  • La propia función podría quitarse en favor de la conversión automática de cadenas por el marco.

CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string? callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

Si una instancia de IJSUnmarshalledObjectReference no se elimina en el código de C#, se puede eliminar en JS. La siguiente función de dispose elimina la referencia de objeto cuando se llama desde JS:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Los tipos de matriz se pueden convertir a partir de objetos JS en objetos .NET mediante js_typed_array_to_array, pero la matriz de JS debe ser una matriz con tipo. Las matrices de JS se pueden leer en código de C# como una matriz de objetos .NET (object[]).

Otros tipos de datos, como las matrices de cadenas, se pueden convertir pero requieren la creación de un nuevo objeto de matriz Mono (mono_obj_array_new) y el establecimiento de su valor (mono_obj_array_set).

Advertencia

Las funciones de JS proporcionadas por el marco de Blazor, como js_typed_array_to_array, mono_obj_array_new, y mono_obj_array_set, están sujetas a cambios de nombre, cambios de comportamiento o eliminación en versiones futuras de .NET.

Eliminación de referencias de objetos de interoperabilidad de JavaScript

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

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

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

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

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

Llamadas de interoperabilidad de JavaScript sin un circuito

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

Recursos adicionales