Condividi tramite


Chiamare le funzioni JavaScript dai metodi .NET in ASP.NET Core Blazor

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo illustra come richiamare funzioni JavaScript (JS) da .NET.

Per informazioni su come chiamare metodi .NET da JS, vedere Chiamare metodi .NET da funzioni JavaScript in ASP.NET Core Blazor.

Richiamare JS le funzioni

IJSRuntime viene registrato dal Blazor framework. Per chiamare JS da .NET, inserire l'astrazione IJSRuntime e chiamare uno dei metodi seguenti:

Per i metodi .NET precedenti che richiamano JS le funzioni:

  • L'identificatore della funzione (String) è relativo all'ambito globale (window). Per chiamare window.someScope.someFunction, l'identificatore è someScope.someFunction. Non è necessario registrare la funzione prima che venga chiamata.
  • Passare un numero qualsiasi di argomenti serializzabili JSON in Object[] a una JS funzione.
  • Il token di annullamento (CancellationToken) propaga una notifica che le operazioni devono essere annullate.
  • TimeSpan rappresenta un limite di tempo per un'operazione JS .
  • Il TValue tipo restituito deve anche essere serializzabile JSON. TValue deve corrispondere al tipo .NET che esegue il mapping migliore al tipo JSON restituito.
  • Viene JS Promise restituito un oggetto per InvokeAsync i metodi. InvokeAsync annulla il wrapping di Promise e restituisce il valore atteso dall'oggetto Promise.

Per Blazor le app con prerendering abilitato, ovvero l'impostazione predefinita per le app sul lato server, la chiamata a JS non è possibile durante la pre-esecuzione del pre-esecuzione. Per altre informazioni, vedere la sezione Prerendering .

L'esempio seguente si basa su TextDecoder, un JSdecodificatore basato su . L'esempio illustra come richiamare una JS funzione da un metodo C# che esegue l'offload di un requisito dal codice dello sviluppatore a un'API esistente JS . La JS funzione accetta una matrice di byte da un metodo C#, decodifica la matrice e restituisce il testo al componente per la visualizzazione.

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

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

Componente seguente:

  • Richiama la convertArrayJS funzione con InvokeAsync quando si seleziona un pulsante (Convert Array).
  • Dopo aver chiamato la JS funzione, la matrice passata viene convertita in una stringa. La stringa viene restituita al componente per la visualizzazione (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));
}

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 JavaScript limitata ai movimenti dell'utente

Questa sezione si applica ai componenti lato server.

Alcune API JavaScript (JS) del browser possono essere eseguite solo nel contesto di un movimento dell'utente, ad esempio usando la Fullscreen API (documentazione MDN). Queste API non possono essere chiamate tramite il JS meccanismo di interoperabilità nei componenti lato server perché la gestione degli eventi dell'interfaccia utente viene eseguita in modo asincrono e in genere non più nel contesto del movimento dell'utente. L'app deve gestire completamente l'evento dell'interfaccia utente in JavaScript, quindi usare onclick anziché Blazorl'attributo di direttiva.@onclick

Richiamare le funzioni JavaScript senza leggere un valore restituito (InvokeVoidAsync)

Usare InvokeVoidAsync quando:

  • .NET non è necessario per leggere il risultato di una chiamata JavaScript (JS).
  • JS le funzioni restituiscono void(0)/void 0 o non definiti.

Specificare una displayTickerAlert1JS funzione. La funzione viene chiamata con InvokeVoidAsync e non restituisce un valore:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

Esempio di componente (.razor) (InvokeVoidAsync)

TickerChanged chiama il handleTickerChanged1 metodo nel componente seguente.

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 string? stockSymbol;
    private decimal price;

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

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 string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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 string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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 string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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);
    }
}

Esempio di classe (.cs) (InvokeVoidAsync)

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

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
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);

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => 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 chiama il handleTickerChanged1 metodo nel componente seguente.

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 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' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

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 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' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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 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' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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 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' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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();
}

Richiamare le funzioni JavaScript e leggere un valore restituito (InvokeAsync)

Usare InvokeAsync quando .NET deve leggere il risultato di una chiamata JavaScript (JS).

Specificare una displayTickerAlert2JS funzione. Nell'esempio seguente viene restituita una stringa per la visualizzazione da parte del chiamante:

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

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

Esempio di componente (.razor) (InvokeAsync)

TickerChanged chiama il handleTickerChanged2 metodo e visualizza la stringa restituita nel componente seguente.

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 string? stockSymbol;
    private decimal price;
    private string? result;

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

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 string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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 string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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 string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.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}";
    }
}

Esempio di classe (.cs) (InvokeAsync)

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) => 
        await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
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) => 
        await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => 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 chiama il handleTickerChanged2 metodo e visualizza la stringa restituita nel componente seguente.

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 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' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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();
}

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 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' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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 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' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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 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' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.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();
}

Scenari di generazione dinamica del contenuto

Per la generazione di contenuto dinamico con BuildRenderTree, usare l'attributo [Inject] :

[Inject]
IJSRuntime JS { get; set; }

Prerendering

Questa sezione si applica alle app lato server che prerendere Razor i componenti. La prerendering è descritta nei componenti Prerender ASP.NET CoreRazor.

Nota

Lo spostamento interno per il routing interattivo in Blazor Web Apps non comporta la richiesta di nuovo contenuto della pagina dal server. Di conseguenza, il prerendering non si verifica per le richieste di pagina interne. Se l'app adotta il routing interattivo, eseguire un ricaricamento a pagina completa per esempi di componenti che illustrano il comportamento di pre-esecuzione. Per altre informazioni, vedere Prerender ASP.NET Componenti di baseRazor.

Questa sezione si applica alle app lato server e alle app ospitate Blazor WebAssembly che prerendere Razor i componenti. La prerendering è descritta in Integrare componenti ASP.NET Core Razor con MVC o Razor Pages.

Durante la pre-esecuzione, la chiamata a JavaScript (JS) non è possibile. Nell'esempio seguente viene illustrato come usare JS l'interoperabilità come parte della logica di inizializzazione di un componente in modo compatibile con la prerendering.

La funzione seguente scrollElementIntoView :

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

Dove IJSRuntime.InvokeAsync chiama la JS funzione nel codice del componente, ElementReference viene usato solo in OnAfterRenderAsync e non in alcun metodo del ciclo di vita precedente perché non è presente alcun elemento DOM HTML fino a quando non viene eseguito il rendering del componente.

StateHasChanged(origine di riferimento) viene chiamato per accodare il rerendering del componente con il nuovo stato ottenuto dalla JS chiamata di interoperabilità (per altre informazioni, vedere rendering dei componenti principali Razor ASP.NET). Non viene creato un ciclo infinito perché StateHasChanged viene chiamato solo quando scrollPosition è null.

PrerenderedInterop.razor:

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

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

L'esempio precedente inquina il client con una funzione globale. Per un approccio migliore nelle app di produzione, vedere Isolamento JavaScript nei moduli JavaScript.

Interoperabilità sincrona JS nei componenti lato client

Questa sezione si applica solo ai componenti lato client.

JS le chiamate di interoperabilità sono asincrone, indipendentemente dal fatto che il codice chiamato sia sincrono o asincrono. Le chiamate sono asincrone per garantire che i componenti siano compatibili tra le modalità di rendering lato server e lato client. Nel server tutte le JS chiamate di interoperabilità devono essere asincrone perché vengono inviate tramite una connessione di rete.

Se si è certi che il componente viene eseguito solo in WebAssembly, è possibile scegliere di effettuare chiamate di interoperabilità sincrone JS . Questo comporta un sovraccarico leggermente inferiore rispetto all'esecuzione di chiamate asincrone e può comportare un minor numero di cicli di rendering perché non esiste uno stato intermedio in attesa dei risultati.

Per effettuare una chiamata sincrona da .NET a JavaScript in un componente lato client, eseguire il cast IJSRuntime per IJSInProcessRuntime effettuare la JS chiamata di interoperabilità:

@inject IJSRuntime JS

...

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

Quando si usa IJSObjectReference in ASP.NET Componenti lato client core 5.0 o versioni successive, è possibile usare IJSInProcessObjectReference invece in modo sincrono. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable e deve essere eliminato per l'operazione di Garbage Collection per evitare una perdita di memoria, come illustrato nell'esempio seguente:

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

Nell'esempio precedente, un JSDisconnectedException oggetto non viene intrappolato durante l'eliminazione del modulo perché non c'è alcun BlazorSignalR circuito in un'app Blazor WebAssembly da perdere. Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Percorso JavaScript

Caricare il codice JavaScript (JS) usando uno degli approcci descritti dall'articolo sul percorso JavaScript:

Per informazioni sull'isolamento degli script nei JS moduli, vedere la sezione Isolamento JavaScript nei moduli JavaScript.

Avviso

Inserire un <script> tag in un file di componente (.razor) solo se il componente deve adottare il rendering statico lato server (SSR statico) perché il <script> tag non può essere aggiornato in modo dinamico.

Avviso

Non inserire un <script> tag in un file di componente (.razor) perché il <script> tag non può essere aggiornato in modo dinamico.

Isolamento di JavaScript nei moduli di JavaScript

Blazor consente l'isolamento di JavaScript (JS) nei moduli JavaScript standard (specifica ECMAScript). Il caricamento del modulo JavaScript funziona allo stesso modo Blazor in quanto funziona per altri tipi di app Web e puoi personalizzare il modo in cui i moduli vengono definiti nell'app. Per una guida su come usare i moduli JavaScript, vedere Documentazione Web MDN: moduli JavaScript.

L'isolamento di JS offre i vantaggi seguenti:

  • Il codice JS importato non inquina più lo spazio dei nomi globale.
  • I consumer di una libreria e dei componenti non devono importare il codice JS correlato.

L'importazione dinamica con l'operatore import() è supportata con ASP.NET Core e Blazor:

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

Nell'esempio precedente il {CONDITION} segnaposto rappresenta un controllo condizionale per determinare se il modulo deve essere caricato.

Per la compatibilità del browser, vedere È possibile usare i moduli JavaScript: importazione dinamica.

Negli scenari lato server non JS è possibile eseguire chiamate di interoperabilità dopo Blazorla disconnessione del circuito.SignalR Senza un circuito durante l'eliminazione di un componente o in qualsiasi altro momento in cui un circuito non esiste, le chiamate al metodo seguenti hanno esito negativo e registrano un messaggio che indica che il circuito viene disconnesso come JSDisconnectedException:

Per evitare la registrazione JSDisconnectedException o registrare informazioni personalizzate sul lato Blazorserver, intercettare l'eccezione in un'istruzione try-catch .

Per l'esempio di eliminazione dei componenti seguente:

  • Il componente lato server implementa IAsyncDisposable.
  • module è un IJSObjectReference oggetto per un JS modulo.
  • JSDisconnectedException viene intercettata e non registrata.
  • Facoltativamente, è possibile registrare informazioni personalizzate nell'istruzione catch a qualsiasi livello di log preferito. L'esempio seguente non registra informazioni personalizzate. Il codice presuppone che lo sviluppatore non si preoccupa di quando o dove i circuiti vengono disconnessi durante l'eliminazione del componente.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Se è necessario pulire i propri JS oggetti o eseguire altro JS codice sul client dopo la perdita di un circuito in un'app lato Blazor server, usare il MutationObserver modello in JS nel client. Il MutationObserver modello consente di eseguire JS il codice quando un elemento viene rimosso dal DOM.

Per altre informazioni, vedere gli articoli seguenti:

Il modulo seguente JS esporta una JS funzione per visualizzare un prompt della finestra del browser. Inserire il codice seguente JS in un file esterno JS .

wwwroot/scripts.js:

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

Aggiungere il modulo precedente JS a un'app o a una libreria di classi come asset Web statico nella wwwroot cartella e quindi importare il modulo nel codice .NET chiamando InvokeAsync sull'istanza IJSRuntime di .

IJSRuntime importa il modulo come IJSObjectReference, che rappresenta un riferimento a un JS oggetto dal codice .NET. IJSObjectReference Usare per richiamare le funzioni esportate JS dal modulo.

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 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)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

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 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)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

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

Nell'esempio precedente:

  • Per convenzione, l'identificatore import è un identificatore speciale usato in modo specifico per l'importazione di un JS modulo.
  • Specificare il file esterno JS del modulo usando il percorso stabile dell'asset Web statico: ./{SCRIPT PATH AND FILE NAME (.js)}, dove:
    • Il segmento di percorso per la directory corrente (./) è necessario per creare il percorso dell'asset statico corretto del file JS.
    • Il segnaposto {SCRIPT PATH AND FILE NAME (.js)} corrisponde al percorso e al nome del file in wwwroot.
  • Elimina l'oggetto per l'operazione IJSObjectReference di Garbage Collection in IAsyncDisposable.DisposeAsync.

L'importazione dinamica di un modulo richiede una richiesta di rete, quindi può essere ottenuta solo in modo asincrono chiamando InvokeAsync.

IJSInProcessObjectReference rappresenta un riferimento a un JS oggetto le cui funzioni possono essere richiamate in modo sincrono nei componenti lato client. Per altre informazioni, vedere la sezione Interoperabilità sincrona JS nei componenti lato client.

Nota

Quando il file esterno JS viene fornito da unaRazor libreria di classi, specificare il file del modulo usando il percorso stabile dell'asset JS Web statico: : ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}

  • Il segmento di percorso per la directory corrente (./) è necessario per creare il percorso dell'asset statico corretto del file JS.
  • Il segnaposto {PACKAGE ID} è l'ID pacchetto della libreria. Per impostazione predefinita, l'ID pacchetto è il nome dell'assembly del progetto, se <PackageId> non è specificato nel file di progetto. Nell'esempio seguente il nome dell'assembly della libreria è ComponentLibrary e il file di progetto della libreria non specifica <PackageId>.
  • Il segnaposto {SCRIPT PATH AND FILE NAME (.js)} corrisponde al percorso e al nome del file in wwwroot. Nell'esempio seguente il file esterno JS (script.js) viene inserito nella cartella della libreria di wwwroot classi.
  • module è un valore Nullable IJSObjectReference privato della classe component (private IJSObjectReference? module;).
module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Per altre informazioni, vedere Utilizzare i componenti ASP.NET Core Razor di una libreria di classi Razor (RCL).

In tutta la Blazor documentazione, gli esempi usano l'estensione .js di file per i file di modulo, non l'estensione di file più recente .mjs (RFC 9239). La documentazione continua a usare l'estensione .js di file per gli stessi motivi per cui la documentazione di Mozilla Foundation continua a usare l'estensione di .js file. Per altre informazioni, vedere Aside — .mjs e .js (documentazione MDN).

Acquisire riferimenti agli elementi

Alcuni scenari di interoperabilità JavaScript (JS) richiedono riferimenti agli elementi HTML. Ad esempio, una libreria dell'interfaccia utente potrebbe richiedere un riferimento a un elemento per l'inizializzazione oppure potrebbe essere necessario chiamare API simili a comandi in un elemento, ad esempio click o play.

Acquisire i riferimenti agli elementi HTML in un componente usando l'approccio seguente:

  • Aggiungere un @ref attributo all'elemento HTML.
  • Definire un campo di tipo ElementReference il cui nome corrisponde al valore dell'attributo @ref .

L'esempio seguente mostra l'acquisizione di un riferimento all'elemento username <input> :

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

@code {
    private ElementReference username;
}

Avviso

Usare solo un riferimento a un elemento per modificare il contenuto di un elemento vuoto che non interagisce con Blazor. Questo scenario è utile quando un'API di terze parti fornisce contenuto all'elemento . Poiché Blazor non interagisce con l'elemento, non esiste alcuna possibilità di conflitto tra Blazorla rappresentazione dell'elemento e il DOM.

Nell'esempio seguente è pericoloso modificare il contenuto dell'elenco non ordinato (ul) usando MyList l'interoperabilità JS perché Blazor interagisce con il DOM per popolare gli elementi dell'elenco di questo elemento (<li>) dall'oggetto Todos :

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

L'uso del riferimento all'elemento MyList per leggere semplicemente il contenuto DOM o attivare un evento è supportato.

Se JS l'interoperabilità modifica il contenuto dell'elemento MyList e Blazor tenta di applicare differenze all'elemento, le differenze non corrispondono al DOM. La modifica del contenuto dell'elenco tramite JS interoperabilità con il riferimento all'elemento MyList non è supportata.

Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Un ElementReference oggetto viene passato al JS codice tramite JS interoperabilità. Il JS codice riceve un'istanza HTMLElement che può essere usata con le normali API DOM. Ad esempio, il codice seguente definisce un metodo di estensione .NET (TriggerClickEvent) che consente di inviare un clic del mouse a un elemento.

La JS funzione clickElement crea un click evento sull'elemento HTML passato (element):

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

Per chiamare una JS funzione che non restituisce un valore, usare JSRuntimeExtensions.InvokeVoidAsync. Il codice seguente attiva un evento lato click client chiamando la funzione precedente JS con l'oggetto acquisito ElementReference:

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

Per usare un metodo di estensione, creare un metodo di estensione statico che riceve l'istanza IJSRuntime :

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

Il clickElement metodo viene chiamato direttamente sull'oggetto . Nell'esempio seguente si presuppone che il TriggerClickEvent metodo sia disponibile dallo spazio dei JsInteropClasses nomi :

@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 exampleButton variabile viene popolata solo dopo il rendering del componente. Se un oggetto non popolato ElementReference viene passato al JS codice, il JS codice riceve il valore .null Per modificare i riferimenti agli elementi al termine del rendering del componente, utilizzare i metodi del ciclo di vita dei OnAfterRenderAsync componenti o OnAfterRender .

Quando si usano tipi generici e si restituisce un valore, usare ValueTask<TResult>:

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

Il {JAVASCRIPT FUNCTION} segnaposto è l'identificatore della JS funzione.

GenericMethod viene chiamato direttamente sull'oggetto con un tipo . Nell'esempio seguente si presuppone che GenericMethod sia disponibile dallo spazio dei JsInteropClasses nomi :

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

Elementi di riferimento tra componenti

Non è possibile passare un oggetto ElementReference tra i componenti perché:

Affinché un componente padre renda disponibile un riferimento a un elemento ad altri componenti, il componente padre può:

  • Consenti ai componenti figlio di registrare i callback.
  • Richiamare i callback registrati durante l'evento con il riferimento all'elemento OnAfterRender passato. Indirettamente, questo approccio consente ai componenti figlio di interagire con il riferimento all'elemento padre.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

CallJs7.razor (componente padre):

@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?" />

CallJs7.razor (componente padre):

@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 padre):

@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 padre):

@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 padre):

@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 padre):

@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)
        {
            subscription.OnNext(title);
        }
    }

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

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)
        {
            subscription.OnNext(title);
        }
    }

    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)
        {
            subscription.OnNext(title);
        }
    }

    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)
        {
            subscription.OnNext(title);
        }
    }

    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)
            {
                subscription.OnNext(title);
            }
        }

        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)
            {
                subscription.OnNext(title);
            }
        }

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

Nell'esempio precedente lo spazio dei nomi dell'app è BlazorSample. Se si testa il codice in locale, aggiornare lo spazio dei nomi.

SurveyPrompt.razor (componente figlio):

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

Nell'esempio precedente lo spazio dei nomi dell'app è BlazorSample con componenti condivisi nella Shared cartella . Se si testa il codice in locale, aggiornare lo spazio dei nomi.

Rafforzare le chiamate di interoperabilità JavaScript

Questa sezione si applica solo ai componenti di Interactive Server, ma i componenti lato client possono anche impostare JS timeout di interoperabilità se le condizioni lo giustificano.

Nelle app lato server con interattività server, l'interoperabilità JavaScript (JS) potrebbe non riuscire a causa di errori di rete e deve essere considerata non affidabile. Blazor le app usano un timeout di un minuto per JS le chiamate di interoperabilità. Se un'app può tollerare un timeout più aggressivo, impostare il timeout usando uno degli approcci seguenti.

Impostare un timeout globale in Program.cs con CircuitOptions.JSInteropDefaultCallTimeout:

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

Impostare un timeout globale nel Startup.ConfigureServices metodo di Startup.cs con CircuitOptions.JSInteropDefaultCallTimeout:

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

Il {TIMEOUT} segnaposto è un oggetto TimeSpan , ad esempio TimeSpan.FromSeconds(80).

Impostare un timeout per chiamata nel codice del componente. Il timeout specificato esegue l'override del timeout globale impostato da JSInteropDefaultCallTimeout:

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

Nell'esempio precedente:

  • Il {TIMEOUT} segnaposto è un oggetto TimeSpan , ad esempio TimeSpan.FromSeconds(80).
  • Il {ID} segnaposto è l'identificatore della funzione da richiamare. Ad esempio, il valore someScope.someFunction richiama la funzione window.someScope.someFunction.

Anche se una causa comune di errori di interoperabilità è costituita da errori di JS rete con componenti lato server, i timeout per chiamata possono essere impostati per JS le chiamate di interoperabilità per i componenti lato client. Anche se no Blazor-SignalR esiste un circuito per un componente lato client, JS le chiamate di interoperabilità potrebbero non riuscire per altri motivi che si applicano.

Per altre informazioni sull'esaurimento delle risorse, vedere Linee guida per la mitigazione delle minacce per ASP.NET rendering lato server interattivo CoreBlazor.

Evitare riferimenti a oggetti circolari

Gli oggetti che contengono riferimenti circolari non possono essere serializzati nel client per:

  • Chiamate al metodo .NET.
  • Chiamate al metodo JavaScript da C# quando il tipo restituito ha riferimenti circolari.

Librerie JavaScript che eseguono il rendering dell'interfaccia utente

A volte è possibile usare librerie JavaScript (JS) che producono elementi dell'interfaccia utente visibili all'interno del DOM del browser. A prima vista, questo potrebbe sembrare difficile perché Blazoril sistema di differenze si basa sul controllo sull'albero degli elementi DOM e si verifica in errori se un codice esterno modifica l'albero DOM e invalida il suo meccanismo per l'applicazione di differenze. Questa non è una Blazorlimitazione specifica. La stessa sfida si verifica con qualsiasi framework dell'interfaccia utente basato su diff.

Fortunatamente, è semplice incorporare in modo affidabile l'interfaccia utente generata esternamente all'interno di un'interfaccia Razor utente del componente. La tecnica consigliata consiste nel produrre un elemento vuoto nel codice del componente (.razor file). Per quanto Blazorriguarda il sistema di differenze, l'elemento è sempre vuoto, quindi il renderer non si ripete nell'elemento e lascia invece solo il suo contenuto. In questo modo è possibile popolare l'elemento con contenuto arbitrario gestito esternamente.

L'esempio seguente illustra il concetto. All'interno dell'istruzione quando è , interagire con l'esterno unmanagedElement dell'interoperabilità Blazor JS.truefirstRender if Ad esempio, chiamare una libreria esterna JS per popolare l'elemento. Blazor lascia solo il contenuto dell'elemento fino a quando questo componente non viene rimosso. Quando il componente viene rimosso, viene rimosso anche l'intero sottoalbero DOM 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)
        {
            ...
        }
    }
}

Si consideri l'esempio seguente che esegue il rendering di una mappa interattiva usando le API Mapbox open source.

Il modulo seguente JS viene inserito nell'app o reso disponibile da una libreria di Razor classi.

Nota

Per creare la mappa Mapbox , ottenere un token di accesso da Mapbox Sign in e specificarlo dove {ACCESS TOKEN} appare nel codice seguente.

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

Per produrre stili corretti, aggiungere il tag del foglio di stile seguente alla pagina HTML host.

Aggiungere l'elemento seguente <link> al markup dell'elemento <head> (posizione del <head> contenuto):

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

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

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

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

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

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

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

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

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

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

L'esempio precedente produce un'interfaccia utente mappa interattiva. L'utente:

  • Può trascinare per scorrere o zoom.
  • Selezionare i pulsanti per passare a posizioni predefinite.

Mappa stradale mapbox di Tokyo, Giappone con pulsanti per selezionare Bristol, Regno Unito e Tokyo, Giappone

Nell'esempio precedente:

  • L'oggetto <div> con @ref="mapElement" viene lasciato vuoto per quanto Blazor riguarda. Lo mapbox-gl.js script può popolare in modo sicuro l'elemento e modificarne il contenuto. Usare questa tecnica con qualsiasi JS libreria che esegue il rendering dell'interfaccia utente. È possibile incorporare componenti da un framework SPA di terze parti JS all'interno Razor dei componenti, purché non tentino di raggiungere e modificare altre parti della pagina. Non è sicuro che il codice esterno JS modifichi gli elementi che Blazor non considerano vuoti.
  • Quando si usa questo approccio, tenere presenti le regole su come Blazor conservare o distruggere gli elementi DOM. Il componente gestisce in modo sicuro gli eventi di clic del pulsante e aggiorna l'istanza di mappa esistente perché gli elementi DOM vengono mantenuti laddove possibile. Se si esegue il rendering di un elenco di elementi della mappa dall'interno di un @foreach ciclo, si vuole usare @key per garantire la conservazione delle istanze del componente. In caso contrario, le modifiche apportate ai dati dell'elenco potrebbero causare la conservazione dello stato delle istanze precedenti in modo indesiderato. Per altre informazioni, vedere come usare l'attributo @key di direttiva per mantenere la relazione tra elementi, componenti e oggetti modello.
  • L'esempio incapsula JS la logica e le dipendenze all'interno di un modulo JavaScript e carica il modulo in modo dinamico usando l'identificatore import . Per altre informazioni, vedere Isolamento JavaScript nei moduli JavaScript.

Supporto della matrice di byte

Blazor supporta l'interoperabilità JavaScript (JS) della matrice di byte ottimizzata che evita la codifica/decodifica delle matrici di byte in Base64. L'esempio seguente usa JS l'interoperabilità per passare una matrice di byte a JavaScript.

Specificare una receiveByteArrayJS funzione. La funzione viene chiamata con InvokeVoidAsync e non restituisce un valore:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return str;
  };
</script>

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

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

Per informazioni sull'uso di una matrice di byte quando si chiama .NET da JavaScript, vedere Chiamare metodi .NET da funzioni JavaScript in ASP.NET Core Blazor.

Trasmettere da .NET a JavaScript

Blazor supporta lo streaming di dati direttamente da .NET a JavaScript (JS). I flussi vengono creati usando un oggetto DotNetStreamReference.

DotNetStreamReference rappresenta un flusso .NET e usa i parametri seguenti:

  • stream: flusso inviato a JS.
  • leaveOpen: determina se il flusso viene lasciato aperto dopo la trasmissione. Se non viene specificato un valore, leaveOpen per impostazione predefinita è false.

In JSusare un buffer di matrice o un flusso leggibile per ricevere i dati:

  • Uso di un oggetto ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Uso di un oggetto ReadableStream:

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

Nel codice C#:

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

Nell'esempio precedente:

  • Il {STREAM} segnaposto rappresenta l'oggetto Stream inviato a JS.
  • JS è un'istanza inserita IJSRuntime .

L'eliminazione di un'istanza DotNetStreamReference in genere non è necessaria. Quando leaveOpen è impostato sul valore predefinito di false, l'oggetto sottostante Stream viene eliminato automaticamente dopo la trasmissione a JS.

Se leaveOpen è true, l'eliminazione di un DotNetStreamReference oggetto non elimina l'oggetto sottostante Stream. Il codice dell'app determina quando eliminare l'oggetto sottostante Stream. Quando si decide come eliminare l'oggetto sottostante Stream, considerare quanto segue:

  • L'eliminazione di un Stream periodo durante la trasmissione a JS viene considerata un errore dell'applicazione e può causare un'eccezione non gestita.
  • Stream la trasmissione inizia non appena DotNetStreamReference viene passato come argomento a una JS chiamata di interoperabilità, indipendentemente dal fatto che il flusso venga effettivamente usato nella JS logica.

Data queste caratteristiche, è consigliabile eliminare l'oggetto sottostante Stream solo dopo che è completamente utilizzato da JS (la promessa restituita da arrayBuffer o stream risolve). Ne consegue che un DotNetStreamReference oggetto deve essere passato JS solo se verrà utilizzato in modo incondizionato dalla JS logica.

Chiamare i metodi .NET dalle funzioni JavaScript in ASP.NET Core Blazor illustra l'operazione inversa, il flusso da JavaScript a .NET.

ASP.NET download di file core Blazor illustra come scaricare un file in Blazor.

Rilevare le eccezioni JavaScript

Per intercettare JS le eccezioni, eseguire il wrapping dell'interoperabilità JS in un-trycatch blocco e intercettare un oggetto .JSException

Nell'esempio seguente la nonFunctionJS funzione non esiste. Quando la funzione non viene trovata, l'oggetto JSException viene intrappolato con un Message che indica l'errore seguente:

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

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

Interrompere una funzione JavaScript a esecuzione prolungata

Usare abortController JScon un CancellationTokenSource nel componente per interrompere una funzione JavaScript a esecuzione prolungata dal codice C#.

La classe seguente JSHelpers contiene una funzione simulata a esecuzione prolungata, longRunningFn, da contare continuamente fino a quando non viene chiamato .AbortController.signal AbortController.abort La sleep funzione è a scopo dimostrativo per simulare l'esecuzione lenta della funzione a esecuzione prolungata e non sarebbe presente nel codice di produzione. Quando un componente chiama stopFn, viene segnalato l'interruzione longRunningFn tramite il controllo condizionale del while ciclo in 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

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

Componente seguente:

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 console degli strumenti di sviluppo di un browser indica l'esecuzione della funzione a esecuzione prolungata JS dopo che il Start Task pulsante è selezionato e quando la funzione viene interrotta dopo che il Cancel Task pulsante è selezionato:

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

Interoperabilità JavaScript [JSImport]/[JSExport]

Questa sezione si applica ai componenti lato client.

In alternativa all'interazione con JavaScript (JS) nei componenti lato client usando Blazoril meccanismo di JS interoperabilità basato sull'interfaccia IJSRuntime , un'API JS[JSImport]/[JSExport] di interoperabilità è disponibile per le app destinate a .NET 7 o versione successiva.

Per altre informazioni, vedere Interoperabilità JSImport/JSExport JavaScript con ASP.NET Core Blazor.

Interoperabilità JavaScript unmarshalled

Questa sezione si applica ai componenti lato client.

L'interoperabilità unmarshalled tramite l'interfaccia IJSUnmarshalledRuntime è obsoleta e deve essere sostituita con l'interoperabilità JavaScript/[JSImport][JSExport].

Per altre informazioni, vedere Interoperabilità JSImport/JSExport JavaScript con ASP.NET Core Blazor.

Interoperabilità JavaScript unmarshalled

Blazor WebAssembly I componenti possono riscontrare prestazioni scarse quando gli oggetti .NET vengono serializzati per l'interoperabilità JavaScript (JS) e uno dei seguenti è vero:

  • Un volume elevato di oggetti .NET viene serializzato rapidamente. Ad esempio, le prestazioni scarse possono risultare quando JS vengono effettuate chiamate di interoperabilità sulla base dello spostamento di un dispositivo di input, ad esempio la rotazione di una rotellina del mouse.
  • Gli oggetti .NET di grandi dimensioni o molti oggetti .NET devono essere serializzati per JS l'interoperabilità. Ad esempio, le prestazioni scarse possono risultare quando JS le chiamate di interoperabilità richiedono la serializzazione di decine di file.

IJSUnmarshalledObjectReference rappresenta un riferimento a un JS oggetto le cui funzioni possono essere richiamate senza l'overhead della serializzazione dei dati .NET.

Nell'esempio seguente :

  • Uno struct contenente una stringa e un numero intero viene passato nonrializzato a JS.
  • JS le funzioni elaborano i dati e restituiscono un valore booleano o una stringa al chiamante.
  • Una JS stringa non è convertibile direttamente in un oggetto .NET string . La unmarshalledFunctionReturnString funzione chiama BINDING.js_string_to_mono_string per gestire la conversione di una JS stringa.

Nota

Gli esempi seguenti non sono casi d'uso tipici per questo scenario perché lo struct passato a JS non comporta prestazioni di componenti scarse. L'esempio usa solo un oggetto di piccole dimensioni per illustrare i concetti per il passaggio di dati .NET nonrializzati.

<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

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

Avviso

Il nome della funzione, il js_string_to_mono_string comportamento e l'esistenza sono soggetti a modifiche in una versione futura di .NET. Ad esempio:

  • È probabile che la funzione venga rinominata.
  • La funzione stessa potrebbe essere rimossa a favore della conversione automatica delle stringhe dal framework.

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

Se un'istanza IJSUnmarshalledObjectReference non viene eliminata nel codice C#, può essere eliminata in JS. La funzione seguente dispose elimina il riferimento all'oggetto quando viene chiamato da JS:

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

    ...
  };
}

I tipi di matrice possono essere convertiti da JS oggetti in oggetti .NET usando js_typed_array_to_array, ma la JS matrice deve essere una matrice tipizzata. Le matrici da JS possono essere lette nel codice C# come matrice di oggetti .NET (object[]).

Altri tipi di dati, ad esempio le matrici di stringhe, possono essere convertiti, ma richiedono la creazione di un nuovo oggetto matrice Mono () e l'impostazione del relativo valore (mono_obj_array_newmono_obj_array_set).

Avviso

JS Le funzioni fornite dal Blazor framework, ad esempio js_typed_array_to_array, mono_obj_array_newe mono_obj_array_set, sono soggette a modifiche del nome, modifiche comportamentali o rimozione nelle versioni future di .NET.

Eliminazione dei riferimenti agli oggetti di interoperabilità JavaScript

Esempi in tutti gli articoli di interoperabilità JavaScript (JS) illustrano i modelli di eliminazione di oggetti tipici:

JS I riferimenti agli oggetti di interoperabilità vengono implementati come mappa con chiave da un identificatore sul lato della JS chiamata di interoperabilità che crea il riferimento. Quando l'eliminazione di oggetti viene avviata da .NET o JS lato, Blazor rimuove la voce dalla mappa e l'oggetto può essere sottoposto a Garbage Collection purché non sia presente alcun altro riferimento sicuro all'oggetto.

Eliminare sempre gli oggetti creati sul lato .NET per evitare perdite di memoria gestita .NET.

Attività di pulizia DOM durante l'eliminazione dei componenti

Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Chiamate di interoperabilità JavaScript senza circuito

Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Risorse aggiuntive