Wywoływanie funkcji JavaScript z metod platformy .NET w programie ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W tym artykule wyjaśniono, jak wywoływać funkcje języka JavaScript (JS) z platformy .NET.

Aby uzyskać informacje na temat wywoływania metod .NET z JSprogramu , zobacz Wywoływanie metod .NET z funkcji Języka JavaScript w programie ASP.NET Core Blazor.

Wywoływanie JS funkcji

IJSRuntime program jest zarejestrowany przez platformę Blazor . Aby wywołać metodę z JS platformy .NET, wstrzyknąć IJSRuntime abstrakcję i wywołać jedną z następujących metod:

W przypadku powyższych metod platformy .NET, które wywołują JS funkcje:

  • Identyfikator funkcji (String) jest względny względem zakresu globalnego (window). Aby wywołać window.someScope.someFunctionmetodę , identyfikator to someScope.someFunction. Nie ma potrzeby rejestrowania funkcji przed jej wywołaniami.
  • Przekaż dowolną JS liczbę argumentów JSmożliwych do serializacji w Object[] funkcji.
  • Token anulowania (CancellationToken) propaguje powiadomienie, że operacje powinny zostać anulowane.
  • TimeSpan reprezentuje limit czasu dla JS operacji.
  • Zwracany TValue typ musi być JSrównież włączony serializowalne. TValue powinien być zgodny z typem platformy .NET, który najlepiej mapuje zwracany JStyp ON.
  • Element A JS Promise jest zwracany dla InvokeAsync metod. InvokeAsync odpakowuje Promise element i zwraca wartość oczekiwaną Promiseprzez element .

W przypadku Blazor aplikacji z włączonym prerenderingiem, które jest ustawieniem domyślnym dla aplikacji po stronie serwera, wywoływanie metody nie JS jest możliwe podczas prerenderingu. Aby uzyskać więcej informacji, zobacz sekcję Prerendering .

Poniższy przykład jest oparty na dekoderze opartym na TextDecodermetodzie JS. W tym przykładzie pokazano, jak wywołać JS funkcję z metody języka C#, która odciąża wymaganie od kodu dewelopera do istniejącego JS interfejsu API. Funkcja JS akceptuje tablicę bajtów z metody języka C#, dekoduje tablicę i zwraca tekst do składnika do wyświetlania.

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

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

Następujący składnik:

  • convertArrayJS Wywołuje funkcję za InvokeAsync pomocą polecenia podczas wybierania przycisku (Convert Array).
  • Po wywołaniu funkcji przekazana JS tablica jest konwertowana na ciąg. Ciąg jest zwracany do składnika do wyświetlania (text).

CallJs1.razor:

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

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

Interfejs API języka JavaScript ograniczony do gestów użytkownika

Ta sekcja dotyczy składników po stronie serwera.

Niektóre interfejsy API języka JavaScript (JS) przeglądarki można wykonywać tylko w kontekście gestu użytkownika, takiego jak użycie Fullscreen API (dokumentacja MDN). Te interfejsy API nie mogą być wywoływane za pośrednictwem JS mechanizmu międzyoperacyjnego w składnikach po stronie serwera, ponieważ obsługa zdarzeń interfejsu użytkownika jest wykonywana asynchronicznie i ogólnie nie jest już w kontekście gestu użytkownika. Aplikacja musi obsługiwać zdarzenie interfejsu użytkownika całkowicie w języku JavaScript, dlatego należy użyć onclick zamiast Blazoratrybutu @onclick dyrektywy .

Wywoływanie funkcji Języka JavaScript bez odczytywania zwróconej wartości (InvokeVoidAsync)

Użyj InvokeVoidAsync polecenia , gdy:

  • Platforma .NET nie jest wymagana do odczytania wyniku wywołania języka JavaScript (JS).
  • JS funkcje zwracają void(0)/void 0 lub niezdefiniowane.

displayTickerAlert1JS Podaj funkcję. Funkcja jest wywoływana z elementem InvokeVoidAsync i nie zwraca wartości:

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

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

Przykład składnika (.razor) (InvokeVoidAsync)

TickerChanged wywołuje metodę handleTickerChanged1 w następującym składniku.

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

Przykład klasy (.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);
    }

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChanged wywołuje metodę handleTickerChanged1 w następującym składniku.

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

Wywoływanie funkcji Języka JavaScript i odczytywanie zwróconej wartości (InvokeAsync)

Użyj InvokeAsync polecenia , gdy platforma .NET powinna odczytać wynik wywołania języka JavaScript (JS).

displayTickerAlert2JS Podaj funkcję. Poniższy przykład zwraca ciąg do wyświetlenia przez obiekt wywołujący:

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

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

Przykład składnika (.razor) (InvokeAsync)

TickerChanged wywołuje metodę handleTickerChanged2 i wyświetla zwrócony ciąg w następującym składniku.

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

Przykład klasy (.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)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChanged wywołuje metodę handleTickerChanged2 i wyświetla zwrócony ciąg w następującym składniku.

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

Scenariusze dynamicznego generowania zawartości

W przypadku dynamicznego generowania zawartości za pomocą elementu BuildRenderTree użyj atrybutu [Inject] :

[Inject]
IJSRuntime JS { get; set; }

Prerendering

Ta sekcja dotyczy aplikacji po stronie serwera, które prerender Razor składniki. Prerendering jest omówiony w składnikach prerender ASP.NET CoreRazor.

Uwaga

Wewnętrzna nawigacja na potrzeby routingu interakcyjnego w Blazor usłudze Web Apps nie obejmuje żądania nowej zawartości strony z serwera. W związku z tym wstępne przetwarzanie nie występuje w przypadku żądań stron wewnętrznych. Jeśli aplikacja przyjmuje routing interakcyjny, wykonaj ponowne ładowanie pełnej strony dla przykładów składników, które pokazują zachowanie wstępne. Aby uzyskać więcej informacji, zobacz Prerender ASP.NET Core components (Składniki prerender ASP.NET CoreRazor).

Ta sekcja dotyczy aplikacji po stronie serwera i hostowanych Blazor WebAssembly aplikacji, które prerender Razor składniki. Prerendering jest omówiony w środowisku Prerender i integruje składniki ASP.NET CoreRazor.

Chociaż aplikacja jest wstępna, niektóre akcje, takie jak wywoływanie kodu JavaScript (JS), nie są możliwe.

W poniższym przykładzie setElementText1 funkcja jest wywoływana z elementem JSRuntimeExtensions.InvokeVoidAsync i nie zwraca wartości.

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

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

Ostrzeżenie

Powyższy przykład modyfikuje dom bezpośrednio tylko w celach demonstracyjnych. Bezpośrednie modyfikowanie modelu DOM JS przy użyciu polecenia nie jest zalecane w większości scenariuszy, ponieważ JS może zakłócać Blazorśledzenie zmian. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Zdarzenie OnAfterRender{Async} cyklu życia nie jest wywoływane podczas procesu wstępnego przetwarzania na serwerze. Zastąp metodę OnAfterRender{Async} , aby opóźnić JS wywołania międzyoperacyjne do momentu renderowania i interakcyjnego składnika na kliencie po wstępnie utworzonym zakończeniu.

PrerenderedInterop1.razor:

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

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

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

@code {
    private ElementReference divElement;

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

Uwaga

Powyższy przykład zanieczyszcza klienta za pomocą funkcji globalnych. Aby uzyskać lepsze podejście do aplikacji produkcyjnych, zobacz izolację języka JavaScript w modułach JavaScript.

Przykład:

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

Poniższy składnik pokazuje, jak używać JS międzyoperacyjności w ramach logiki inicjowania składnika w sposób zgodny z prerenderingiem. Składnik pokazuje, że można wyzwolić aktualizację renderowania z wewnątrz OnAfterRenderAsyncelementu . Deweloper musi zachować ostrożność, aby uniknąć tworzenia nieskończonej pętli w tym scenariuszu.

W poniższym przykładzie setElementText2 funkcja jest wywoływana z wartością IJSRuntime.InvokeAsync i zwraca wartość.

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

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

Ostrzeżenie

Powyższy przykład modyfikuje dom bezpośrednio tylko w celach demonstracyjnych. Bezpośrednie modyfikowanie modelu DOM JS przy użyciu polecenia nie jest zalecane w większości scenariuszy, ponieważ JS może zakłócać Blazorśledzenie zmian. Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Gdzie JSRuntime.InvokeAsync jest wywoływana ElementReference , parametr jest używany tylko w OnAfterRenderAsync metodzie cyklu życia i nie we wcześniejszej metodzie cyklu życia, ponieważ nie ma elementu DOM HTML do momentu renderowania składnika.

StateHasChangedjest wywoływana w celu rerender składnika o nowym stanie uzyskanym JS z wywołania międzyoperaciowego (aby uzyskać więcej informacji, zobacz ASP.NET Renderowanie składników CoreRazor). Kod nie tworzy nieskończonej pętli, ponieważ StateHasChanged jest wywoływany tylko wtedy, gdy data ma wartość null.

PrerenderedInterop2.razor:

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

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

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

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



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

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

            StateHasChanged();
        }
    }
}

Uwaga

Powyższy przykład zanieczyszcza klienta za pomocą funkcji globalnych. Aby uzyskać lepsze podejście do aplikacji produkcyjnych, zobacz izolację języka JavaScript w modułach JavaScript.

Przykład:

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

Synchroniczne międzyoperajności JS w składnikach po stronie klienta

Ta sekcja dotyczy tylko składników po stronie klienta.

Wywołania współdziałania języka JS są domyślnie asynchroniczne niezależnie od tego, czy wywoływany kod jest synchroniczny, czy asynchroniczny. Wywołania są domyślnie asynchroniczne, aby upewnić się, że składniki są zgodne z trybami renderowania po stronie serwera i po stronie klienta. Na serwerze wszystkie JS wywołania międzyoperacyjne muszą być asynchroniczne, ponieważ są wysyłane za pośrednictwem połączenia sieciowego.

Jeśli wiesz, że składnik działa tylko w zestawie WebAssembly, możesz wykonać synchroniczne wywołania międzyoperacyjne JS . Ma to nieco mniejsze obciążenie niż wykonywanie wywołań asynchronicznych i może spowodować zmniejszenie liczby cykli renderowania, ponieważ nie ma stanu pośredniego podczas oczekiwania na wyniki.

Aby wykonać synchroniczne wywołanie z platformy .NET do języka JavaScript w składniku po stronie klienta, rzutuj IJSRuntime w celu IJSInProcessRuntime wywołania międzyoperacyjności JS :

@inject IJSRuntime JS

...

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

Podczas pracy ze składnikami IJSObjectReference ASP.NET Core 5.0 lub nowszymi po stronie klienta można użyć IJSInProcessObjectReference synchronicznie. IJSInProcessObjectReference implementuje IAsyncDisposable/IDisposable i należy usunąć odzyskiwanie pamięci, aby zapobiec wyciekowi pamięci, jak pokazano w poniższym przykładzie:

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

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

    ...

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

Lokalizacja języka JavaScript

Załaduj kod JavaScript (JS) przy użyciu dowolnego podejścia opisanego w artykule dotyczącym lokalizacji języka JavaScript:

Aby uzyskać informacje na temat izolowania skryptów w JS modułach, zobacz sekcję Izolacja języka JavaScript w modułach Języka JavaScript.

Ostrzeżenie

Umieść <script> tag tylko w pliku składnika (.razor), jeśli składnik ma gwarancję wdrożenia statycznego renderowania po stronie serwera (statyczny SSR), ponieważ <script> tag nie może być aktualizowany dynamicznie.

Ostrzeżenie

Nie umieszczaj tagu <script> w pliku składnika (.razor), ponieważ <script> tag nie może być aktualizowany dynamicznie.

Izolacja języka JavaScript w modułach języka JavaScript

Platforma Blazor włącza izolację języka JavaScript (JS) w standardowych modułach języka JavaScript (Specyfikacja ECMAScript). Ładowanie modułu JavaScript działa tak samo jak w Blazor przypadku innych typów aplikacji internetowych i możesz dostosować sposób definiowania modułów w aplikacji. Aby zapoznać się z przewodnikiem dotyczącym używania modułów Języka JavaScript, zobacz Artykuł MdN Web Docs: JavaScript modules (Dokumentacja internetowa mdN: moduły Języka JavaScript).

Izolacja języka JS zapewnia następujące korzyści:

  • Zaimportowany kod JS nie zanieczyszcza już globalnej przestrzeni nazw.
  • Użytkownicy biblioteki i składników nie są zobowiązani do zaimportowania powiązanego kodu JS.

Importowanie dynamiczne za pomocą import() operatora jest obsługiwane w przypadku platformy ASP.NET Core i Blazor:

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

W poprzednim przykładzie symbol zastępczy reprezentuje sprawdzanie warunkowe, aby określić, {CONDITION} czy moduł powinien zostać załadowany.

Aby uzyskać informacje o zgodności przeglądarki, zobacz Czy mogę używać: moduły JavaScript: import dynamiczny.

Na przykład poniższy JS moduł eksportuje JS funkcję do wyświetlania monitu o okno przeglądarki. Umieść następujący JS kod w pliku zewnętrznym JS .

wwwroot/scripts.js:

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

Dodaj poprzedni JS moduł do biblioteki aplikacji lub klas jako statyczny zasób internetowy w wwwroot folderze, a następnie zaimportuj moduł do kodu platformy .NET, wywołując InvokeAsyncIJSRuntime wystąpienie.

IJSRuntime importuje moduł jako IJSObjectReferenceelement , który reprezentuje odwołanie do JS obiektu z kodu platformy .NET. Użyj polecenia , IJSObjectReference aby wywołać wyeksportowane JS funkcje z modułu.

CallJs6.razor:

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

<PageTitle>Call JS 6</PageTitle>

<h1>Call JS Example 6</h1>

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

<p>
    @result
</p>

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

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

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

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

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

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

<p>
    @result
</p>

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

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

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

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

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

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

<p>
    @result
</p>

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

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

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

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

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

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

<p>
    @result
</p>

@code {
    private IJSObjectReference module;
    private string result;

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

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

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

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

W powyższym przykładzie:

  • Zgodnie z konwencją import identyfikator jest specjalnym identyfikatorem używanym specjalnie do importowania modułu JS .
  • Określ plik zewnętrzny JS modułu przy użyciu stabilnej statycznej ścieżki zasobu internetowego: ./{SCRIPT PATH AND FILE NAME (.js)}, gdzie:
    • Segment ścieżki dla bieżącego katalogu (./) jest wymagany w celu utworzenia poprawnej statycznej ścieżki zasobów do pliku JS.
    • Symbol zastępczy {SCRIPT PATH AND FILE NAME (.js)} to ścieżka i nazwa pliku w folderze wwwroot.
  • Usuwa element IJSObjectReference do odzyskiwania pamięci w pliku IAsyncDisposable.DisposeAsync.

Dynamiczne importowanie modułu wymaga żądania sieciowego, więc można go osiągnąć asynchronicznie tylko przez wywołanie metody InvokeAsync.

IJSInProcessObjectReference reprezentuje odwołanie do JS obiektu, którego funkcje mogą być wywoływane synchronicznie w składnikach po stronie klienta. Aby uzyskać więcej informacji, zobacz sekcję Synchroniczne międzyoperacji JS w składnikach po stronie klienta.

Uwaga

Gdy plik zewnętrzny JS jest dostarczany przez bibliotekę Razor klas, określ plik modułu JS przy użyciu jego stabilnej statycznej ścieżki zasobu internetowego: : ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}

  • Segment ścieżki dla bieżącego katalogu (./) jest wymagany w celu utworzenia poprawnej statycznej ścieżki zasobów do pliku JS.
  • Symbol zastępczy {PACKAGE ID} to identyfikator pakietu biblioteki. Identyfikator pakietu domyślnie określa nazwę zestawu projektu, jeśli tag <PackageId> nie jest określony w pliku projektu. W poniższym przykładzie nazwa zestawu biblioteki to ComponentLibrary , a plik projektu biblioteki nie określa <PackageId>.
  • Symbol zastępczy {SCRIPT PATH AND FILE NAME (.js)} to ścieżka i nazwa pliku w folderze wwwroot. W poniższym przykładzie plik zewnętrzny JS (script.js) jest umieszczany w folderze biblioteki wwwroot klas.
  • module jest prywatną wartością null IJSObjectReference klasy składnika (private IJSObjectReference? module;).
module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Aby uzyskać więcej informacji, zobacz Korzystanie ze składników platformy ASP.NET Core Razor z biblioteki klas Razor (RCL).

W całej Blazor dokumentacji przykłady używają .js rozszerzenia pliku dla plików modułu, a nie nowszego .mjsrozszerzenia pliku (RFC 9239). Nasza dokumentacja nadal używa .js rozszerzenia pliku z tych samych powodów, dla których dokumentacja Mozilla Foundation nadal używa .js rozszerzenia pliku. Aby uzyskać więcej informacji, zobacz Odkładanie — .mjs a .js (dokumentacja MDN).

Przechwytywanie odwołań do elementów

Niektóre scenariusze międzyoperacyjności języka JavaScript (JS) wymagają odwołań do elementów HTML. Na przykład biblioteka interfejsu użytkownika może wymagać odwołania do elementu do inicjowania lub może być konieczne wywołanie interfejsów API podobnych do poleceń w elemencie, takim jak click lub play.

Przechwyć odwołania do elementów HTML w składniku przy użyciu następującego podejścia:

  • @ref Dodaj atrybut do elementu HTML.
  • Zdefiniuj pole typu ElementReference , którego nazwa odpowiada wartości atrybutu @ref .

W poniższym przykładzie pokazano przechwytywanie odwołania do username<input> elementu:

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

@code {
    private ElementReference username;
}

Ostrzeżenie

Użyj odwołania do elementu, aby zmutować zawartość pustego elementu, który nie wchodzi w interakcję z elementem Blazor. Ten scenariusz jest przydatny, gdy interfejs API innej firmy dostarcza zawartość do elementu. Ponieważ Blazor element nie wchodzi w interakcje, nie ma możliwości konfliktu między Blazorreprezentacją elementu a elementem DOM.

W poniższym przykładzie niebezpieczne jest zmutowanie zawartości nieurządzanej listy () przy użyciu MyList międzyoperacyjnościJS, ponieważ Blazor współdziała z domem w celu wypełnienia elementów listy tego elementu (ul<li>) z Todos obiektu:

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

MyList Używanie odwołania do elementu tylko do odczytywania zawartości DOM lub wyzwalania zdarzenia jest obsługiwane.

Jeśli JS interop wycisza zawartość elementu MyList i Blazor próbuje zastosować różnice do elementu, różnice nie będą zgodne z dom. Modyfikowanie zawartości listy za pośrednictwem JS międzyoperajności z odwołaniem MyList do elementu nie jest obsługiwane.

Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Element ElementReference jest przekazywany do JS kodu za pośrednictwem JS międzyoperacjności. Kod JS odbiera HTMLElement wystąpienie, którego może używać z normalnymi interfejsami API DOM. Na przykład poniższy kod definiuje metodę rozszerzenia .NET (TriggerClickEvent), która umożliwia wysyłanie kliknięcia myszą do elementu.

Funkcja JSclickElement tworzy click zdarzenie dla przekazanego elementu HTML (element):

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

Aby wywołać JS funkcję, która nie zwraca wartości, użyj polecenia JSRuntimeExtensions.InvokeVoidAsync. Poniższy kod wyzwala zdarzenie po stronie click klienta przez wywołanie JS poprzedniej funkcji z przechwyconą ElementReferencefunkcją :

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

Aby użyć metody rozszerzenia, utwórz statyczną metodę rozszerzenia, która odbiera IJSRuntime wystąpienie:

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

Metoda clickElement jest wywoływana bezpośrednio w obiekcie . W poniższym przykładzie przyjęto założenie, że TriggerClickEvent metoda jest dostępna w JsInteropClasses przestrzeni nazw:

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

Ważne

Zmienna exampleButton jest wypełniana tylko po renderowaniu składnika. Jeśli kod zostanie przekazany do kodu, ElementReferenceJS kod otrzyma wartość null.JS Aby manipulować odwołaniami do elementów po zakończeniu renderowania składnika, użyj OnAfterRenderAsync metod cyklu życia składników lubOnAfterRender.

Podczas pracy z typami ogólnymi i zwracania wartości użyj polecenia ValueTask<TResult>:

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

Symbol {JAVASCRIPT FUNCTION} zastępczy jest identyfikatorem JS funkcji.

GenericMethod obiekt jest wywoływany bezpośrednio na obiekcie o typie. W poniższym przykładzie przyjęto założenie, że GenericMethod element jest dostępny w JsInteropClasses przestrzeni nazw:

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

Odwołania do elementów między składnikami

ElementReference Nie można przekazać elementu między składnikami, ponieważ:

Aby składnik nadrzędny udostępnić odwołanie do innych składników, składnik nadrzędny może:

  • Zezwalaj składnikom podrzędnym na rejestrowanie wywołań zwrotnych.
  • Wywołaj zarejestrowane wywołania zwrotne podczas OnAfterRender zdarzenia za pomocą przekazanego odwołania do elementu. Pośrednio takie podejście umożliwia składnikom podrzędnym interakcję z odwołaniem do elementu nadrzędnego.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

CallJs7.razor (składnik nadrzędny):

@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 (składnik nadrzędny):

@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 (składnik nadrzędny):

@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 (składnik nadrzędny):

@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 (składnik nadrzędny):

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

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

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

CallJs7.razor.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

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

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

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

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();

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

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages;

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

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

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

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

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

CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages;

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

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

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

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

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

CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

W poprzednim przykładzie przestrzeń nazw aplikacji to BlazorSample. W przypadku lokalnego testowania kodu zaktualizuj przestrzeń nazw.

SurveyPrompt.razor (składnik podrzędny):

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

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

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

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

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

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

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

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

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

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

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

SurveyPrompt.razor.cs:

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

namespace BlazorSample.Components;

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

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

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

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

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

    public void OnCompleted()
    {
        subscription = null;
    }

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

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

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

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

namespace BlazorSample.Shared;

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

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

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

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

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

    public void OnCompleted()
    {
        subscription = null;
    }

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

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

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

namespace BlazorSample.Shared;

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

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

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

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

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

    public void OnCompleted()
    {
        subscription = null;
    }

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

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

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

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

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

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

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

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

        public void OnCompleted()
        {
            subscription = null;
        }

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

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

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

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

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

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

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

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

        public void OnCompleted()
        {
            subscription = null;
        }

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

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

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

W poprzednim przykładzie przestrzeń nazw aplikacji jest BlazorSample z udostępnionymi składnikami w folderze Shared . W przypadku lokalnego testowania kodu zaktualizuj przestrzeń nazw.

Wzmacnianie połączeń międzyoperacyjnych języka JavaScript

Ta sekcja dotyczy tylko składników interactive server, ale składniki po stronie klienta mogą również ustawiać JS limity czasu międzyoperacyjności, jeśli warunki go uzasadniają.

W aplikacjach po stronie serwera z interakcyjnością serwera współdziałanie języka JavaScript (JS) może zakończyć się niepowodzeniem z powodu błędów sieci i powinno być traktowane jako zawodne. Domyślnie Blazor aplikacje używają limitu czasu jednej minuty dla JS wywołań międzyoperaowych. Jeśli aplikacja może tolerować bardziej agresywny limit czasu, ustaw limit czasu przy użyciu jednej z poniższych metod.

Ustaw globalny limit czasu w elemencji za Program.cs pomocą CircuitOptions.JSInteropDefaultCallTimeoutpolecenia :

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

Ustaw globalny limit czasu w metodzie Startup.cs za Startup.ConfigureServices pomocą CircuitOptions.JSInteropDefaultCallTimeoutpolecenia :

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

Symbol {TIMEOUT} zastępczy to TimeSpan (na przykład TimeSpan.FromSeconds(80)).

Ustaw limit czasu wywołania w kodzie składnika. Określony limit czasu zastępuje globalny limit czasu ustawiony przez JSInteropDefaultCallTimeout:

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

W powyższym przykładzie:

  • Symbol {TIMEOUT} zastępczy to TimeSpan (na przykład TimeSpan.FromSeconds(80)).
  • Symbol {ID} zastępczy jest identyfikatorem funkcji do wywołania. Na przykład wartość someScope.someFunction wywołuje funkcję window.someScope.someFunction.

Chociaż częstą przyczyną JS niepowodzeń międzyoperacyjnych są błędy sieciowe ze składnikami po stronie serwera, limity czasu wywołania można ustawić dla JS wywołań międzyoperacyjnych dla składników po stronie klienta. Mimo że nie SignalR istnieje obwód dla składnika po stronie klienta, JS wywołania międzyoperaciowe mogą zakończyć się niepowodzeniem z innych powodów, które mają zastosowanie.

Aby uzyskać więcej informacji na temat wyczerpania zasobów, zobacz Wskazówki dotyczące ograniczania zagrożeń dotyczące renderowania interaktywnego po stronie serwera ASP.NET CoreBlazor.

Unikaj odwołań do obiektów okrągłych

Obiekty zawierające odwołania cykliczne nie mogą być serializowane na kliencie dla jednego z następujących obiektów:

  • Wywołania metody .NET.
  • Wywołania metody Języka JavaScript z języka C#, gdy zwracany typ zawiera odwołania cykliczne.

Biblioteki języka JavaScript renderujące interfejs użytkownika

Czasami warto użyć bibliotek Języka JavaScript (JS), które generują widoczne elementy interfejsu użytkownika w przeglądarce DOM. Na pierwszy rzut oka może się to wydawać trudne, ponieważ Blazorsystem różnicowania polega na kontroli nad drzewem elementów DOM i występuje w błędach, jeśli niektóre zewnętrzne kody wyciszają drzewo DOM i unieważniają jego mechanizm stosowania różnic. Nie jest Blazorto ograniczenie specyficzne. To samo wyzwanie występuje w przypadku dowolnej platformy interfejsu użytkownika opartej na różnicach.

Na szczęście interfejs użytkownika generowany zewnętrznie można niezawodnie osadzić w Razor interfejsie użytkownika składnika. Zalecaną techniką jest utworzenie pustego elementu w kodzie (.razor pliku) składnika. Jeśli chodzi o Blazorsystem różnicowania, element jest zawsze pusty, więc moduł renderujący nie powraca do elementu i zamiast tego pozostawia jego zawartość sam. Dzięki temu można bezpiecznie wypełnić element dowolną zawartością zarządzaną zewnętrznie.

W poniższym przykładzie przedstawiono koncepcję. W instrukcji if when firstRender to true, wchodzić w interakcje poza unmanagedElement użyciem JS międzyoperacjnościBlazor. Na przykład wywołaj bibliotekę zewnętrzną JS , aby wypełnić element. Blazor pozostawia zawartość samego elementu do momentu usunięcia tego składnika. Po usunięciu składnika cały poddrzewo DOM składnika również zostanie usunięte.

<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)
        {
            ...
        }
    }
}

Rozważmy poniższy przykład, który renderuje mapę interaktywną przy użyciu interfejsów API mapbox typu open source.

JS Poniższy moduł zostanie umieszczony w aplikacji lub udostępniony z Razor biblioteki klas.

Uwaga

Aby utworzyć mapę Mapbox , uzyskaj token dostępu z usługi Mapbox Sign in i podaj go, gdzie {ACCESS TOKEN} znajduje się w poniższym kodzie.

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

Aby utworzyć poprawny styl, dodaj następujący tag arkusza stylów do strony HTML hosta.

Dodaj następujący <link> element do <head> znaczników elementu (lokalizacja <head> zawartości):

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

CallJs8.razor:

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

<PageTitle>Call JS 8</PageTitle>

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

Powyższy przykład tworzy interaktywny interfejs użytkownika mapy. Użytkownik:

  • Można przeciągnąć, aby przewinąć lub powiększyć.
  • Wybierz przyciski, aby przejść do wstępnie zdefiniowanych lokalizacji.

Mapbox mapa ulic Tokio, Japonia z przyciskami, aby wybrać Bristol, Wielka Brytania i Tokio, Japonia

W powyższym przykładzie:

  • Z <div> jest @ref="mapElement" pozostawiony pusty, jeśli Blazor chodzi o to. Skrypt mapbox-gl.js może bezpiecznie wypełnić element i zmodyfikować jego zawartość. Użyj tej techniki z dowolną JS biblioteką renderowaną przez interfejs użytkownika. Składniki można osadzać z platformy SPA innej firmy JS wewnątrz Razor składników, o ile nie próbują skontaktować się i zmodyfikować inne części strony. Kod zewnętrzny JS nie jest bezpieczny, aby modyfikować elementy, które Blazor nie są uważane za puste.
  • W przypadku korzystania z tego podejścia należy pamiętać o zasadach zachowania Blazor lub zniszczenia elementów DOM. Składnik bezpiecznie obsługuje zdarzenia kliknięcia przycisku i aktualizuje istniejące wystąpienie mapy, ponieważ elementy DOM są zachowywane tam, gdzie to możliwe domyślnie. Jeśli renderowano listę elementów mapy z wewnątrz @foreach pętli, chcesz użyć @key polecenia , aby zapewnić zachowanie wystąpień składników. W przeciwnym razie zmiany w danych listy mogą spowodować, że wystąpienia składników zachowają stan poprzednich wystąpień w niepożądany sposób. Aby uzyskać więcej informacji, zobacz, jak używać atrybutu @key dyrektywy w celu zachowania relacji między elementami, składnikami i obiektami modelu.
  • Przykład hermetyzuje JS logikę i zależności w module ES6 i ładuje moduł dynamicznie przy użyciu identyfikatora import . Aby uzyskać więcej informacji, zobacz Izolacja języka JavaScript w modułach Języka JavaScript.

Obsługa tablic bajtów

Blazor obsługuje zoptymalizowaną międzyoperacyjną tablicę bajtów JavaScript (JS), która pozwala uniknąć kodowania/dekodowania tablic bajtowych do base64. W poniższym przykładzie użyto JS międzyoperacji w celu przekazania tablicy bajtów do języka JavaScript.

receiveByteArrayJS Podaj funkcję. Funkcja jest wywoływana z elementem InvokeVoidAsync i nie zwraca wartości:

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

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach 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);
    }
}

Aby uzyskać informacje na temat używania tablicy bajtów podczas wywoływania platformy .NET z języka JavaScript, zobacz Wywoływanie metod platformy .NET z funkcji języka JavaScript w programie ASP.NET Core Blazor.

Przesyłanie strumieniowe z platformy .NET do języka JavaScript

Blazor obsługuje przesyłanie strumieniowe danych bezpośrednio z platformy .NET do języka JavaScript. Strumienie są tworzone przy użyciu elementu DotNetStreamReference.

DotNetStreamReference reprezentuje strumień platformy .NET i używa następujących parametrów:

  • stream: strumień wysłany do języka JavaScript.
  • leaveOpen: określa, czy strumień jest pozostawiony otwarty po transmisji. Jeśli wartość nie jest podana, leaveOpen wartość domyślna to false.

W języku JavaScript użyj buforu tablicy lub strumienia czytelnego, aby odbierać dane:

  • Przy użyciu elementu ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Przy użyciu elementu ReadableStream:

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

W kodzie języka C#:

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

W powyższym przykładzie:

  • Symbol {STREAM} zastępczy reprezentuje Stream wysłane do języka JavaScript.
  • JS jest wystąpieniem wstrzykniętym IJSRuntime .

Wywołanie metod platformy .NET z funkcji Języka JavaScript w programie ASP.NET Core Blazor obejmuje operację odwrotną, przesyłaną strumieniowo z języka JavaScript do platformy .NET.

pliki ASP.NET Core Blazor obejmują sposób pobierania pliku w programie Blazor.

Przechwytywanie wyjątków języka JavaScript

Aby przechwycić JS wyjątki, opakuj międzyoperajności JS w-trycatchbloku i przechwyć JSExceptionelement .

W poniższym przykładzie nonFunctionJS funkcja nie istnieje. Gdy funkcja nie zostanie znaleziona, JSException element jest uwięziony za pomocą elementu Message , który wskazuje następujący błąd:

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

CallJs11.razor:

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

<PageTitle>Call JS 11</PageTitle>

<h1>Call JS Example 11</h1>

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

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

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

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

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

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

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

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

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

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

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

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

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

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

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

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

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

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

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

Przerywanie długotrwałej funkcji języka JavaScript

JSUżyj abortController z elementem CancellationTokenSource w składniku, aby przerwać długotrwałą funkcję Języka JavaScript z kodu języka C#.

Poniższa JSHelpers klasa zawiera symulowaną długotrwałą funkcję , longRunningFndo liczenia w sposób ciągły do AbortController.signal momentu AbortController.abort wywołania metody . Funkcja sleep służy do celów demonstracyjnych, aby symulować powolne wykonywanie długotrwałej funkcji i nie byłoby obecne w kodzie produkcyjnym. Gdy składnik wywołuje stopFnmetodę , longRunningFn element jest sygnalizowany, aby przerwać za pośrednictwem sprawdzania warunkowego while pętli w systemie 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>

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

Następujący składnik:

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

Konsola narzędzi deweloperskich przeglądarki wskazuje wykonywanie długotrwałej JS funkcji po wybraniu Start Task przycisku, a po wybraniu funkcji zostanie przerwana po wybraniu Cancel Task przycisku:

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

Interopcja języka JavaScript [JSImport]/[JSExport]

Ta sekcja dotyczy składników po stronie klienta.

Alternatywą dla interakcji z językiem JavaScript (JS) w składnikach po stronie klienta przy użyciu JSBlazormechanizmu międzyoperacyjnego opartego na interfejsie IJSRuntime jest/JS[JSImport][JSExport] dostępny interfejs API międzyoperacyjny dla aplikacji przeznaczonych dla platformy .NET 7 lub nowszej.

Aby uzyskać więcej informacji, zobacz JavaScript Import/Export interop with ASP.NET Core (Importowanie/JSeksportowanie międzyoperacji języka JavaScript JSza pomocą programu ASP.NET CoreBlazor).

Unmarshalled międzyoperacyjności języka JavaScript

Ta sekcja dotyczy składników po stronie klienta.

Unmarshalled interop przy użyciu interfejsu IJSUnmarshalledRuntime jest przestarzały i powinien zostać zastąpiony międzyoperacyjnością języka JavaScript/[JSImport][JSExport].

Aby uzyskać więcej informacji, zobacz JavaScript Import/Export interop with ASP.NET Core (Importowanie/JSeksportowanie międzyoperacji języka JavaScript JSza pomocą programu ASP.NET CoreBlazor).

Unmarshalled międzyoperacyjności języka JavaScript

Blazor WebAssembly Składniki mogą mieć niską wydajność, gdy obiekty platformy .NET są serializowane dla międzyoperacyjności języka JavaScript (JS), a jedną z następujących wartości jest spełniony:

  • Duża liczba obiektów platformy .NET jest szybko serializowana. Na przykład niska wydajność może spowodować, że JS wywołania międzyoperamentowe są wykonywane na podstawie przenoszenia urządzenia wejściowego, takiego jak obracanie kółka myszy.
  • Duże obiekty platformy .NET lub wiele obiektów platformy .NET muszą być serializowane na potrzeby JS międzyoperacyjności. Na przykład niska wydajność może spowodować, że JS wywołania międzyoperacjonowe wymagają serializacji kilkudziesięciu plików.

IJSUnmarshalledObjectReference reprezentuje odwołanie do JS obiektu, którego funkcje można wywołać bez konieczności serializacji danych platformy .NET.

W poniższym przykładzie:

  • Struktura zawierająca ciąg i liczba całkowita jest przekazywana nieserializowane do JS.
  • JS funkcje przetwarzają dane i zwracają wartość logiczną lub ciąg do obiektu wywołującego.
  • Ciąg JS nie jest bezpośrednio konwertowany na obiekt .NET string . Funkcja unmarshalledFunctionReturnString wywołuje BINDING.js_string_to_mono_string metodę zarządzania konwersją JS ciągu.

Uwaga

Poniższe przykłady nie są typowymi przypadkami użycia w tym scenariuszu, ponieważ struktura przekazana do JS nie powoduje niskiej wydajności składników. W tym przykładzie użyto tylko małego obiektu, aby zademonstrować koncepcje przekazywania nieserializowanych danych platformy .NET.

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

Uwaga

Aby uzyskać ogólne wskazówki dotyczące JS lokalizacji i naszych zaleceń dotyczących aplikacji produkcyjnych, zobacz Lokalizacja języka JavaScript w aplikacjach ASP.NET CoreBlazor.

Ostrzeżenie

Nazwa js_string_to_mono_string , zachowanie i istnienie funkcji mogą ulec zmianie w przyszłej wersji platformy .NET. Na przykład:

  • Prawdopodobnie zostanie zmieniona nazwa funkcji.
  • Sama funkcja może zostać usunięta na rzecz automatycznej konwersji ciągów przez platformę.

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

IJSUnmarshalledObjectReference Jeśli wystąpienie nie jest usuwane w kodzie języka C#, można go usunąć w pliku JS. Następująca dispose funkcja usuwa odwołanie do obiektu po wywołaniu z elementu JS:

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

    ...
  };
}

Typy tablic można konwertować z JS obiektów na obiekty platformy .NET przy użyciu klasy js_typed_array_to_array, ale tablica JS musi być tablicą typową. Tablice z JS programu można odczytać w kodzie języka C# jako tablicę obiektów platformy .NET (object[]).

Inne typy danych, takie jak tablice ciągów, można przekonwertować, ale wymagają utworzenia nowego obiektu tablicy Mono (mono_obj_array_new) i ustawienia jego wartości (mono_obj_array_set).

Ostrzeżenie

JS funkcje udostępniane przez platformę Blazor , takie jak js_typed_array_to_array, mono_obj_array_newi mono_obj_array_set, podlegają zmianom nazw, zmianom behawioralnym lub usunięciu w przyszłych wersjach platformy .NET.

Usuwanie odwołań do obiektów międzyoperacyjnych języka JavaScript

Przykłady w artykułach międzyoperacyjnych języka JavaScript (JS) przedstawiają typowe wzorce usuwania obiektów:

JS Odwołania do obiektów międzyoperacyjności są implementowane jako mapowane przez identyfikator po stronie wywołania międzyoperacyjnego JS , które tworzy odwołanie. Gdy usuwanie obiektu jest inicjowane z platformy .NET lub JS po stronie, Blazor usuwa wpis z mapy, a obiekt może zostać odśmiecany, o ile nie ma innego silnego odwołania do obiektu.

Co najmniej zawsze usuwaj obiekty utworzone po stronie platformy .NET, aby uniknąć wycieku pamięci zarządzanej platformy .NET.

Zadania oczyszczania modelu DOM podczas usuwania składników

Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Wywołania międzyoperacyjne języka JavaScript bez obwodu

Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Dodatkowe zasoby