Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье объясняется, как вызывать функции JavaScript (JS) из .NET.

Сведения о том, как вызывать методы .NET из JS, смотрите в разделе Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core.

JS Вызов функций

IJSRuntime регистрируется платформой Blazor. Для вызова JS из .NET внедрите абстракцию IJSRuntime и вызовите один из следующих методов:

Для предыдущих методов .NET, которые вызывают функции JS:

  • Идентификатор функции (String) задается относительно глобальной области (window). Чтобы вызвать функцию window.someScope.someFunction, необходимо использовать идентификатор someScope.someFunction. Регистрировать функцию перед ее вызовом не требуется.
  • Передайте любое количество аргументов, сериализуемых в JSON, в Object[] для функции JS.
  • Токен отмены (CancellationToken) распространяет уведомление о том, что операции должны быть отменены.
  • TimeSpan представляет предельное время для операции JS.
  • Тип возвращаемого значения TValue также должен сериализоваться в JSON. Тип TValue должен соответствовать типу .NET, который лучше всего соответствует возвращаемому типу JSON.
  • Возвращается InvokeAsync для JS Promise методов. InvokeAsync распаковывает Promise и возвращает значение, ожидаемое Promise.

Для Blazor приложений с включенной предварительной отрисовкой, которая используется по умолчанию для серверных приложений, вызов JS не возможен во время предварительной отрисовки. Дополнительные сведения см. в разделе Предварительная отрисовка.

Приведенный ниже пример основан на TextDecoder, декодере на базе JS. В примере показано, как вызвать функцию JS из метода C#, которая переносит требование из кода разработчика в существующий API JS. Функция JS принимает массив байтов из метода C#, декодирует его и возвращает компоненту текст для отображения.

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Приведенный ниже компонент делает следующее.

  • Вызывает функцию JSconvertArray с помощью метода InvokeAsync при нажатии кнопки (Convert Array).
  • После вызова функции JS переданный массив преобразуется в строку. Строка возвращается компоненту для отображения (text).

CallJs1.razor:

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

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

CallJsExample1.razor:

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

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

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

<p>
    @text
</p>

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

@code {
    private MarkupString text;

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

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

API JavaScript ограничены жестами пользователя

Этот раздел относится к компонентам на стороне сервера.

Некоторые API-интерфейсы JavaScript браузера (JS) могут быть выполнены только в контексте жеста пользователя, например, с помощью Fullscreen API (документации MDN). Эти API не могут вызываться через JS механизм взаимодействия в компонентах на стороне сервера, так как обработка событий пользовательского интерфейса выполняется асинхронно и обычно не в контексте жеста пользователя. Приложение может обрабатывать событие интерфейса полностью на JavaScript, поэтому используйте onclick вместо атрибута директивы @onclickBlazor.

Вызов функций JavaScript без считывания возвращаемого значения (InvokeVoidAsync)

InvokeVoidAsync следует использовать в следующих случаях:

  • Если .NET не нужно считывать результат вызова JavaScript (JS).
  • для функций JS, возвращающих значение void(0)/void 0 или undefined.

Укажите функцию JSdisplayTickerAlert1. Функция вызывается с помощью метода InvokeVoidAsync и не возвращает значение:

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Пример (InvokeVoidAsync) компонента (.razor)

TickerChangedhandleTickerChanged1 вызывает метод в следующем компоненте.

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

Пример (InvokeVoidAsync) класса (.cs)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged1 вызывает метод в следующем компоненте.

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

Вызов функций JavaScript и чтение возвращаемого значения (InvokeAsync)

Используйте InvokeAsync, если .NET нужно считать результат вызова JavaScript (JS).

Укажите функцию JSdisplayTickerAlert2. В следующем примере возвращается строка для отображения вызывающим объектом:

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Пример (InvokeAsync) компонента (.razor)

TickerChangedhandleTickerChanged2 вызывает метод и отображает возвращаемую строку в следующем компоненте.

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

Пример (InvokeAsync) класса (.cs)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

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

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged2 вызывает метод и отображает возвращаемую строку в следующем компоненте.

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

Сценарии создания динамического содержимого

Для динамического создания содержимого с помощью BuildRenderTree используйте атрибут [Inject]:

[Inject]
IJSRuntime JS { get; set; }

Предварительная отрисовка

Этот раздел относится к серверным приложениям, которые предопределили Razor компоненты. Предварительная отрисовка рассматривается в компонентах Prerender ASP.NET CoreRazor.

Примечание.

Внутренняя навигация для интерактивной маршрутизации в Blazor веб-приложения не включает запрос нового содержимого страницы с сервера. Поэтому предварительное отображение не выполняется для внутренних запросов страниц. Если приложение принимает интерактивную маршрутизацию, выполните полную перезагрузку страницы для примеров компонентов, демонстрирующих поведение предварительной подготовки. Дополнительные сведения см. в разделе "Предварительная ASP.NET Основные Razor компоненты".

Этот раздел относится к серверным приложениям и размещенным приложениям, которые предустановили Blazor WebAssemblyRazor компоненты. Предварительная отрисовка рассматривается в статье Предварительная отрисовка и интеграция компонентов Razor в ASP.NET Core.

Когда приложение выполняет предварительную отрисовку, некоторые действия, такие как вызов в JavaScript (JS), невозможны.

В следующем примере setElementText1 функция вызывается и JSRuntimeExtensions.InvokeVoidAsync не возвращает значение.

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

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

Предупреждение

Предыдущий пример изменяет только DOM для демонстрационных целей. В большинстве сценариев выполнять непосредственное изменение модели DOM с помощью JS не рекомендуется, так как JS может повлиять на отслеживание изменений Blazor. Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Событие жизненного цикла OnAfterRender{Async} не вызывается при предварительной отрисовке на сервере. Переопределите метод OnAfterRender{Async}, чтобы отложить вызовы взаимодействия с JS до тех пор, пока компонент не будет отрисован и не будет находиться в интерактивном режиме в клиенте после предварительной отрисовки.

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

Примечание.

В предыдущем примере клиент загрязняет глобальные функции. Более эффективный подход для приложений в рабочей среде приведен в разделе Изоляция JavaScript в модулях JavaScript.

Пример:

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

В следующем компоненте показано, как использовать взаимодействие с JS в составе логики инициализации компонента, совместимое с предварительной отрисовкой. Компонент показывает, что обновление отрисовки можно активировать из OnAfterRenderAsync. В этом сценарии разработчику следует избегать создания бесконечного цикла.

В следующем примере setElementText2 функция вызывается и IJSRuntime.InvokeAsync возвращает значение.

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

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

Предупреждение

Предыдущий пример изменяет только DOM для демонстрационных целей. В большинстве сценариев выполнять непосредственное изменение модели DOM с помощью JS не рекомендуется, так как JS может повлиять на отслеживание изменений Blazor. Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Где JSRuntime.InvokeAsyncElementReference вызывается, он используется только в OnAfterRenderAsync более ранних методах жизненного цикла, так как после отрисовки компонента отсутствует элемент HTML DOM.

Метод StateHasChanged вызывается для повторной отрисовки компонента с новым состоянием, полученным из вызова взаимодействия с JS (дополнительные сведения см. в статье Отрисовка компонента приложения Razor в ASP.NET Core). Код не создает бесконечный цикл, так как метод StateHasChanged вызывается, только если data имеет значение 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();
        }
    }
}

Примечание.

В предыдущем примере клиент загрязняет глобальные функции. Более эффективный подход для приложений в рабочей среде приведен в разделе Изоляция JavaScript в модулях JavaScript.

Пример:

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

Синхронная JS взаимодействие в клиентских компонентах

Этот раздел применяется только к клиентским компонентам.

Вызовы взаимодействия с JS по умолчанию выполняются асинхронно и независимо от поддержки синхронности в вызываемом коде. Вызовы являются асинхронными по умолчанию, чтобы обеспечить совместимость компонентов между серверным и клиентским режимами отрисовки. На сервере все JS вызовы взаимодействия должны быть асинхронными, так как они отправляются по сетевому подключению.

Если вы знаете, что компонент выполняется только в WebAssembly, можно сделать синхронные JS вызовы взаимодействия. Они создают чуть меньше нагрузки, чем асинхронные вызовы, и снижают число циклов отрисовки благодаря отсутствию промежуточного состояния на время ожидания результатов.

Чтобы сделать синхронный вызов из .NET в JavaScript в клиентском компоненте, выполните приведение IJSRuntime , чтобы IJSInProcessRuntime сделать JS вызов взаимодействия:

@inject IJSRuntime JS

...

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

При работе с IJSObjectReference компонентами ASP.NET Core 5.0 или более поздней версии можно использовать IJSInProcessObjectReference синхронно. IJSInProcessObjectReferenceIAsyncDisposable/IDisposable реализует и должен быть удален для сборки мусора, чтобы предотвратить утечку памяти, как показано в следующем примере:

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

Расположение JavaScript

Загрузите код JavaScript (JS) с помощью любого из подходов, описанных в статье о расположении JavaScript:

Сведения об изоляции скриптов в модулях JS см. в разделе Изоляция JavaScript в модулях JavaScript.

Предупреждение

Только поместите <script> тег в файл компонента (.razor), если компонент гарантированно принимает статическую отрисовку на стороне сервера (статический SSR), так как <script> тег не может быть динамически обновлен.

Предупреждение

Не помещайте тег <script> в файл компонента (.razor), так как тег <script> не может изменяться динамически.

Изоляция JavaScript в модулях JavaScript

Blazor реализует изоляцию JavaScript (JS) в стандартных модулях JavaScript (см. спецификацию ECMAScript). Загрузка модуля JavaScript работает так же Blazor , как и для других типов веб-приложений, и вы можете настроить способ определения модулей в приложении. Руководство по использованию модулей JavaScript см . в веб-документации MDN: модули JavaScript.

Изоляция JS обеспечивает следующие преимущества:

  • Импортированный JS не засоряет глобальное пространство имен.
  • Пользователям библиотеки и компонентов не требуется импортировать связанный код JS.

Динамический import() импорт с оператором поддерживается с ASP.NET Core и Blazor:

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

В предыдущем примере {CONDITION} заполнитель представляет условный проверка, чтобы определить, следует ли загрузить модуль.

Сведения о совместимости браузера см. в разделе "Можно ли использовать: модули JavaScript: динамический импорт".

Например, следующий модуль JS экспортирует функцию JS для отображения запроса в окне браузера. Поместите следующий код JS во внешний файл JS.

wwwroot/scripts.js:

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

Добавьте предыдущий модуль JS в приложение или библиотеку классов в виде статического веб-ресурса в папке wwwroot, а затем импортируйте модуль в код .NET, вызвав InvokeAsync для экземпляра IJSRuntime.

IJSRuntime импортирует модуль как IJSObjectReference. Это представление ссылки на объект JS из кода .NET. Используйте IJSObjectReference для вызова экспортированных функций JS из модуля.

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

В предыдущем примере:

  • По правилам идентификатор import является особым идентификатором, используемым специально для импорта модуля JS.
  • Укажите внешний JS файл модуля, используя стабильный путь к статическим веб-ресурсам: ./{SCRIPT PATH AND FILE NAME (.js)}где:
    • Сегмент пути для текущего каталога (./) необходим для создания корректного пути к статическому ресурсу в файле JS.
    • Заполнитель {SCRIPT PATH AND FILE NAME (.js)} — это путь и имя файла в папке wwwroot.
  • Уничтожает IJSObjectReference для сборки мусора в IAsyncDisposable.DisposeAsync.

Для динамического импорта модуля требуется сетевой запрос, поэтому его можно выполнить только асинхронно, вызвав InvokeAsync.

IJSInProcessObjectReference представляет ссылку на JS объект, функции которого можно вызывать синхронно в клиентских компонентах. Дополнительные сведения см . в разделе синхронного JS взаимодействия в клиентских компонентах .

Примечание.

Если внешний файл JS предоставляется библиотекой классов Razor, укажите файл JS модуля, используя путь к статическому стабильному веб-ресурсу ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • Сегмент пути для текущего каталога (./) необходим для создания корректного пути к статическому ресурсу в файле JS.
  • Заполнитель {PACKAGE ID} — это идентификатор пакета библиотеки. Идентификатор пакета по умолчанию имеет имя сборки проекта, если значение <PackageId> не указано в файле проекта. В следующем примере имя сборки библиотеки — ComponentLibrary, а в файле проекта библиотеки не указан <PackageId>.
  • Заполнитель {SCRIPT PATH AND FILE NAME (.js)} — это путь и имя файла в папке wwwroot. В следующем примере внешний файл JS (script.js) помещается в папку wwwroot библиотеки классов.
  • module является закрытым значением IJSObjectReference NULL класса компонента (private IJSObjectReference? module;).
module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Дополнительные сведения см. в статье Использование компонентов Razor в ASP.NET Core из библиотеки классов Razor (RCL).

Blazor В документации примеры используют .js расширение файла для файлов модулей, а не более новое .mjs расширение файла (RFC 9239). Наша документация продолжает использовать .js расширение файла по тем же причинам, что документация Mozilla Foundation продолжает использовать .js расширение файла. Дополнительные сведения см. в разделе "В сторону" — MJS и .js (документация по MDN).

Получение ссылок на элементы

В некоторых сценариях взаимодействия с JS требуются ссылки на элементы HTML. Например, ссылка на элемент может требоваться библиотеке пользовательского интерфейса для инициализации либо необходимо вызывать командные интерфейсы API для элемента, такого как click или play.

Для получения ссылок на элементы HTML в компоненте используйте описанный ниже подход.

  • Добавьте атрибут @ref к элементу HTML.
  • Определите поле типа ElementReference, имя которого совпадает со значением атрибута @ref.

В следующем примере показано получение ссылки на элемент username<input>:

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

@code {
    private ElementReference username;
}

Предупреждение

Ссылку на элемент следует использовать только для изменения содержимого пустого элемента, который не взаимодействует с Blazor. Этот сценарий полезен, если сторонний интерфейс API предоставляет содержимое элементу. Так как Blazor не взаимодействует с элементом, риск конфликта между представлением Blazor элемента и моделью DOM отсутствует.

В следующем примере опасно изменить содержимое неупорядоченного списка (ul) с помощью MyListJS взаимодействия, так как Blazor взаимодействует с DOM для заполнения элементов списка этого элемента (<li>) из Todos объекта:

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

MyList Использование ссылки на элемент для простого чтения содержимого DOM или активации события поддерживается.

Если JS взаимодействие мутирует содержимое элемента MyList и Blazor пытается применить диффы к элементу, диффы не будут соответствовать DOM. Изменение содержимого списка с помощью взаимодействия со ссылкой на элемент не поддерживается.MyListJS

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

ElementReference передается в код JS посредством взаимодействия с JS. Код HTMLElement получает экземпляр JS, который может использоваться с обычными интерфейсами API DOM. Например, в приведенном ниже коде определяется метод расширения .NET (TriggerClickEvent), который позволяет отправить щелчок мыши в элемент.

Функция JSclickElement создает событие click в переданном элементе HTML (element):

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

Для вызова функции JS, которая не возвращает значение, используйте метод JSRuntimeExtensions.InvokeVoidAsync. Следующий код активирует событие click на стороне клиента, вызывая предыдущую функцию JS с захваченным ElementReference:

@inject IJSRuntime JS

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

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

@code {
    private ElementReference exampleButton;

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

Чтобы использовать метод расширения, создайте статический метод расширения, который принимает экземпляр IJSRuntime:

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

Метод clickElement вызывается для объекта напрямую. В следующем примере предполагается, что метод TriggerClickEvent доступен из пространства имен JsInteropClasses:

@inject IJSRuntime JS
@using JsInteropClasses

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

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

@code {
    private ElementReference exampleButton;

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

Внимание

Переменная exampleButton заполняется только после отрисовки компонента. Если в код JS передается пустая ссылка ElementReference, код JS получает значение null. Для управления ссылками на элементы после завершения отрисовки компонента используйте методы жизненного цикла компонента OnAfterRenderAsync или OnAfterRender.

При работе с универсальными типами и возврате значения используйте ValueTask<TResult>:

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

Заполнитель {JAVASCRIPT FUNCTION} — это идентификатор функции JS.

Метод GenericMethod вызывается для объекта с типом напрямую. В следующем примере предполагается, что метод GenericMethod доступен из пространства имен JsInteropClasses:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

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

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

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

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

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

Ссылки на элементы между компонентами

ElementReference нельзя передать между компонентами, так как:

Чтобы сделать ссылку на элемент доступной для других компонентов, родительский компонент может:

  • разрешить дочерним компонентам регистрировать обратные вызовы;
  • вызывать зарегистрированные обратные вызовы во время события OnAfterRender с помощью переданной ссылки на элемент. Такой подход позволяет дочерним компонентам взаимодействовать со ссылкой на элемент родительского компонента косвенным образом.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

CallJs7.razor (родительский компонент):

@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 (родительский компонент):

@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 (родительский компонент):

@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 (родительский компонент):

@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 (родительский компонент):

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

В предыдущем примере пространство имен приложения — BlazorSampleэто . При локальном тестировании кода обновите пространство имен.

SurveyPrompt.razor (дочерний компонент):

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

В предыдущем примере в качестве пространства имен приложения указано BlazorSample с общими компонентами в папке Shared. При локальном тестировании кода обновите пространство имен.

Повышение надежности вызовов взаимодействия с JavaScript

Этот раздел применяется только к компонентам интерактивного сервера, но клиентские компоненты также могут задавать JS время ожидания взаимодействия, если условия гарантируют это.

В серверных приложениях с интерактивностью сервера взаимодействие JavaScript (JS) может завершиться ошибкой из-за ошибок сети и должно рассматриваться как ненадежное. По умолчанию приложения Blazor используют время ожидания, равное 1 минуте, для вызовов взаимодействия с JS. Если для приложения допустимо более короткое время ожидания, установите его одним из указанных ниже способов.

Задайте глобальное время ожидания в методе Program.cs с помощью свойства CircuitOptions.JSInteropDefaultCallTimeout:

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

Задайте глобальное время ожидания в методе Startup.ConfigureServices файла Startup.cs с помощью свойства CircuitOptions.JSInteropDefaultCallTimeout:

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

Заполнитель {TIMEOUT} — это TimeSpan заполнитель (например, TimeSpan.FromSeconds(80)).

Задайте время ожидания для отдельного вызова в коде компонента. Указанное время ожидания переопределяет глобальное время ожидания, установленное свойством JSInteropDefaultCallTimeout:

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

В предыдущем примере:

  • Заполнитель {TIMEOUT} — это TimeSpan заполнитель (например, TimeSpan.FromSeconds(80)).
  • Заполнитель {ID} является идентификатором вызываемой функции. Например, значение someScope.someFunction вызывает функцию window.someScope.someFunction.

Хотя распространенными причинами JS сбоев взаимодействия являются сбои сети с компонентами на стороне сервера, время ожидания для каждого вызова можно задать для JS вызовов взаимодействия для клиентских компонентов. Хотя ни один канал не SignalR существует для клиентского компонента, JS вызовы взаимодействия могут завершиться ошибкой по другим причинам, которые применяются.

Дополнительные сведения об исчерпании ресурсов см. в руководстве по устранению угроз для ASP.NET интерактивной отрисовки на стороне сервера ASP.NET CoreBlazor.

Исключение циклических ссылок на объекты

Объекты, содержащие циклические ссылки, не могут быть сериализованы на клиенте для:

  • вызовов метода .NET.
  • Вызов метода JavaScript из C#, если тип возвращаемого значения имеет циклические ссылки.

Библиотеки JavaScript, отображающие пользовательский интерфейс

Иногда может потребоваться использовать библиотеки JavaScript (JS), которые создают видимые элементы пользовательского интерфейса в браузере DOM. На первый взгляд, это может показаться затруднительным, так как система сравнения Blazor предполагает наличие контроля над деревом элементов DOM и в ней возникают ошибки, если какой-либо внешний код изменяет дерево DOM и механизм применения различий становится недействительным. Это ограничение не относится лишь к Blazor. Такая же проблема возникает при использовании любой платформы пользовательского интерфейса на основе сравнения.

К счастью, в пользовательский интерфейс компонента Razor можно легко внедрить внешний пользовательский интерфейс. Рекомендуемым методом является создание пустого элемента кодом компонента (в файле .razor). С точки зрения системы сравнения Blazor элемент всегда пуст, поэтому отрисовщик не выполняет рекурсию в него и не обрабатывает его содержимое. Это позволяет спокойно заполнить элемент произвольным содержимым, управляемым извне.

Данный принцип демонстрируется в приведенном ниже примере. В инструкции if, когда firstRender имеет значение true, используйте unmanagedElement за пределами Blazor с помощью взаимодействия JS. Например, вызовите внешнюю библиотеку JS для заполнения элемента. Blazor оставляет содержимое элемента без изменений, пока сам компонент не будет удален. При удалении компонента удаляется и все его поддерево DOM.

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

Рассмотрим указанный ниже пример, который отрисовывает интерактивную карту с помощью интерфейсов API Mapbox с открытым кодом.

Следующий модуль JS помещается в приложение или предоставляется из библиотеки классов Razor.

Примечание.

Чтобы создать карту Mapbox, получите маркер доступа (для этого перейдите на страницу входа в Mapbox) и укажите его в расположении следующего кода, где отображается {ACCESS TOKEN}.

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

Чтобы обеспечить правильный стиль, добавьте следующий тег таблицы стилей на страницу HTML узла.

Добавьте следующий элемент <link> в разметку элемента <head> (расположение содержимого <head>):

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

CallJs8.razor:

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

<PageTitle>Call JS 8</PageTitle>

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

В предыдущем примере создается интерактивный пользовательский интерфейс карты. Пользователь может выполнять следующие действия:

  • прокручивать карту или изменять масштаб перетаскиванием;
  • переходить к заданным расположениям нажатием кнопок.

Карта улиц Mapbox для Токио (Япония) с кнопками для выбора Бристоля (Великобритания) и Токио (Япония)

В предыдущем примере:

  • В случае с Blazor<div> с @ref="mapElement" остается пустым. С помощью скрипта mapbox-gl.js можно безопасно заполнять элемент и изменять его содержимое. Используйте этот метод с любой библиотекой JS, которая отрисовывает пользовательский интерфейс. В компоненты Razor можно даже внедрять компоненты из сторонней платформы одностраничных приложений JS, если они не пытаются изменить другие части страницы. Ситуация, когда внешний код JS изменяет элементы, которые Blazor не считает пустыми, небезопасна.
  • При использовании этого подхода необходимо учитывать то, как Blazor удерживает или уничтожает элементы DOM. Компонент безопасно обрабатывает события нажатия кнопок и обновляет существующий экземпляр карты, так как по умолчанию элементы DOM по возможности сохраняются без изменений. При отрисовке списка элементов карты из цикла @foreach необходимо использовать @key, чтобы гарантировать сохранность экземпляров компонента. В противном случае изменения в данных списка могут приводить к нежелательному сохранению состояния предыдущих экземпляров компонента. Дополнительные сведения см. в статье об использовании атрибута @key директивы для сохранения связи между элементами, компонентами и объектами модели.
  • В примере выполняется инкапсуляция логики и зависимостей JS в модуле ES6 и динамическая загрузка модуля с помощью идентификатора import. Дополнительные сведения см. в разделе Изоляция JavaScript в модулях JavaScript.

Поддержка массивов байтов

Blazor поддерживает оптимизированное взаимодействие с массивом байтов JavaScript (JS), которое позволяет избежать кодирования и декодирования массивов байтов в Base64. В следующем примере используется взаимодействие JS для передачи массива байтов в JavaScript.

Укажите функцию JSreceiveByteArray. Функция вызывается с помощью метода InvokeVoidAsync и не возвращает значение:

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

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях 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);
    }
}

Сведения об использовании массива байтов при вызове .NET из JavaScript см. в разделе Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core.

Потоковая передача из .NET в JavaScript

Blazor поддерживает потоковую передачу данных непосредственно из .NET в JavaScript. Потоки создаются с помощью метода DotNetStreamReference.

DotNetStreamReference представляет поток .NET и использует следующие параметры:

  • stream: поток, отправленный в JavaScript.
  • leaveOpen: определяет, остается ли поток открытым после передачи. Если значение не указано, для leaveOpen по умолчанию задается false.

В JavaScript для получения данных используйте буфер массива или поток, доступный для чтения.

  • Использование ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Использование ReadableStream:

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

В коде C#:

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

В предыдущем примере:

  • Заполнитель {STREAM} представляет Stream, отправленный в JavaScript.
  • JS является внедренным экземпляром IJSRuntime.

В статье Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core рассматривается обратная операция, потоковая передача данных из JavaScript в .NET.

Статья Загрузки файлов Blazor ASP.NET Core содержит сведения о скачивании файла в Blazor.

Перехват исключений JavaScript

Чтобы перехватить исключения JS, заключите взаимодействие JS в блок try-catch и перехватите JSException.

В следующем примере функция nonFunctionJS не существует. Если функция не найдена, объект JSException перехватывается с помощью Message, который указывает на следующую ошибку:

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

Прерывание длительной функции JavaScript

Используйте JSAbortController с CancellationTokenSource в компоненте, чтобы прервать длительную функцию JavaScript из кода C#.

Следующий класс JSHelpers содержит смоделированную длительную функцию longRunningFn, которая непрерывно будет считать до тех пор, пока AbortController.signal не проинформирует о вызове AbortController.abort. Функция sleep предназначена для демонстрации для имитации медленного выполнения длительной функции. Она не будет присутствовать в рабочем коде. Когда компонент вызывает stopFn, longRunningFn получает сигнал о прерывании через условную проверку цикла while по адресу 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>

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Приведенный ниже компонент делает следующее.

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

Консоль средств разработчика в браузере показывает выполнение длительной функции JS после выбора кнопки Start Task и прерывание выполнения функции после выбора кнопки Cancel Task:

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

Взаимодействие JavaScript [JSImport]/[JSExport]

Этот раздел относится к клиентским компонентам.

В качестве альтернативы взаимодействию с JavaScript (JS) в клиентских компонентах с помощью BlazorJS механизма взаимодействия на IJSRuntime основе интерфейса/JS[JSImport][JSExport] API взаимодействия доступен для приложений, предназначенных для .NET 7 или более поздней версии.

Дополнительные сведения см. в разделе "Импорт иJS экспорт JavaScriptJS" с ASP.NET CoreBlazor.

Демаршалированные вызовы взаимодействия с JavaScript

Этот раздел относится к клиентским компонентам.

Немарсхоллированный взаимодействие с помощью IJSUnmarshalledRuntime интерфейса устарело и должно быть заменено взаимодействием JavaScript/[JSImport][JSExport].

Дополнительные сведения см. в разделе "Импорт иJS экспорт JavaScriptJS" с ASP.NET CoreBlazor.

Демаршалированные вызовы взаимодействия с JavaScript

Производительность компонентов Blazor WebAssembly может снизиться при сериализации объектов .NET для взаимодействия с JavaScript (JS) и наличии одного из следующих условий:

  • Быстро сериализуется большой объем объектов .NET. Например, если выполняются вызовы взаимодействия с JS на основе перемещения устройства ввода, например прокрутки колесика мыши, это может снизить производительность.
  • Для взаимодействия с JS нужно сериализовать большие объекты .NET или много объектов .NET. Например, если для вызовов взаимодействия с JS требуется сериализовать десятки файлов, это может снизить производительность.

IJSUnmarshalledObjectReference представляет ссылку на объект JS, функции которого могут вызываться без дополнительных затрат, связанных с сериализацией данных .NET.

В следующем примере :

  • Структура, содержащая строку и целое число, передается в JS без сериализации.
  • Функции JS обрабатывают данные и возвращают вызывающему объекту логическое значение или строку.
  • Строку JS нельзя напрямую преобразовать в объект string .NET. Функция unmarshalledFunctionReturnString вызывает BINDING.js_string_to_mono_string для управления преобразованием строки JS.

Примечание.

Следующие примеры не являются типичными вариантами использования для этого сценария, так как структура, передаваемая в JS, не приводит к ухудшению производительности компонента. В примере мы используем небольшой объект, только чтобы продемонстрировать концепцию передачи несериализованных данных .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>

Примечание.

Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Предупреждение

Возможно, в одном из будущих выпусков .NET. имя и поведение функции js_string_to_mono_string изменится либо она будет удалена. Например:

  • Скорее всего, функция будет переименована.
  • Возможно, сама функция будет удалена, а вместо нее будет реализовано автоматическое преобразование строк платформой.

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 не удален в коде C#, он может быть удален в JS. Следующая функция dispose удаляет ссылку на объект при вызове из JS:

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

    ...
  };
}

Типы массивов можно преобразовать из объектов JS в объекты .NET с помощью js_typed_array_to_array, но при этом массив JS должен быть типизированным. Массивы из JS могут считываться в коде C# как массив объектов .NET (object[]).

Можно преобразовать и другие типы данных, например массивы строк, но при этом нужно создать новый объект массива Mono (mono_obj_array_new) и задать его значение (mono_obj_array_set).

Предупреждение

Возможно, в будущих выпусках .NET. функции JS (такие как js_typed_array_to_array, mono_obj_array_new и mono_obj_array_set), предоставляемые платформой Blazor, будут удалены либо изменятся их имена или поведение.

Удаление ссылок на объекты взаимодействия JavaScript

Примеры в статьях взаимодействия JavaScript (JS) демонстрируют типичные шаблоны удаления объектов:

JS Ссылки на объекты взаимодействия реализуются в виде карты с ключом идентификатора на стороне JS вызова взаимодействия, создающего ссылку. Если удаление объектов инициируется из .NET или JS на стороне, удаляет запись из карты, Blazor а объект может быть собран мусором до тех пор, пока нет другой строгой ссылки на объект.

Как минимум, всегда удалять объекты, созданные на стороне .NET, чтобы избежать утечки управляемой памяти .NET.

Задачи очистки DOM во время удаления компонентов

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Вызовы взаимодействия JavaScript без канала

Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Дополнительные ресурсы