Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
В этой статье объясняется, как вызывать функции 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
. Регистрировать функцию перед ее вызовом не требуется. - Передайте в функцию JS любое количество аргументов, сериализуемых в
Object[]
формате JSON. - Токен отмены (
CancellationToken
) распространяет уведомление о том, что операции должны быть отменены. TimeSpan
представляет предельное время для операции JS.- Возвращаемый
TValue
тип также должен быть сериализуемым в формате JSON. ТипTValue
должен соответствовать типу .NET, который лучше всего соответствует возвращаемому типу JSON. - Возвращается
InvokeAsync
для JSPromise
методов.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.
Приведенный ниже компонент делает следующее.
- Вызывает функцию JS
convertArray
с помощью метода 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));
}
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
вместо атрибута директивы @onclick
Blazor.
Вызов функций JavaScript без считывания возвращаемого значения (InvokeVoidAsync
)
InvokeVoidAsync следует использовать в следующих случаях:
- Если .NET не нужно считывать результат вызова JavaScript (JS).
- для функций JS, возвращающих значение void(0)/void 0 или undefined.
Укажите функцию JS displayTickerAlert1
. Функция вызывается с помощью метода InvokeVoidAsync и не возвращает значение:
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
Примечание.
Общие рекомендации по JS расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.
Пример (InvokeVoidAsync
) компонента (.razor
)
TickerChanged
handleTickerChanged1
вызывает метод в следующем компоненте.
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
Пример (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);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask TickerChanged(string symbol, decimal price) =>
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
handleTickerChanged1
вызывает метод в следующем компоненте.
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses1(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
Вызов функций JavaScript и чтение возвращаемого значения (InvokeAsync
)
Используйте InvokeAsync, если .NET нужно считать результат вызова JavaScript (JS).
Укажите функцию JS displayTickerAlert2
. В следующем примере возвращается строка для отображения вызывающим объектом:
<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
)
TickerChanged
handleTickerChanged2
вызывает метод и отображает возвращаемую строку в следующем компоненте.
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
Пример (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) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask<string> TickerChanged(string symbol, decimal price) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
handleTickerChanged2
вызывает метод и отображает возвращаемую строку в следующем компоненте.
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses2(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
Сценарии создания динамического содержимого
Для динамического создания содержимого с помощью BuildRenderTree используйте атрибут [Inject]
:
[Inject]
IJSRuntime JS { get; set; }
Предварительная отрисовка
Этот раздел относится к серверным приложениям, которые предопределили Razor компоненты. Предварительная отрисовка рассматривается в компонентах Prerender ASP.NET CoreRazor.
Примечание.
Внутренняя навигация для интерактивной маршрутизации в Blazor Web Apps не включает запрос нового содержимого страницы с сервера. Поэтому предварительное отображение не выполняется для внутренних запросов страниц. Если приложение принимает интерактивную маршрутизацию, выполните полную перезагрузку страницы для примеров компонентов, демонстрирующих поведение предварительной подготовки. Дополнительные сведения см. в разделе "Предварительная ASP.NET Основные Razor компоненты".
Этот раздел относится к серверным приложениям и размещенным приложениям, которые предустановили Blazor WebAssembly Razor компоненты. Предварительная подготовка рассматривается в разделе "Интеграция ASP.NET Основных Razor компонентов с MVC или Razor Pages".
Во время предварительной подготовки вызов в JavaScript (JS) невозможен. В следующем примере показано, как использовать JS взаимодействие в рамках логики инициализации компонента таким образом, который совместим с предварительной подготовкой.
Следующая функция scrollElementIntoView
:
- Прокручивается до переданного элемента с
scrollIntoView
помощью . - Возвращает значение свойства элемента
top
изgetBoundingClientRect
метода.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
При IJSRuntime.InvokeAsync вызове JS функции в коде компонента используется OnAfterRenderAsync только в более ранних методах жизненного цикла, ElementReference так как после отрисовки компонента отсутствует элемент HTML DOM.
StateHasChanged
(ссылочный источник) вызывается для перечисления rerendering компонента с новым состоянием, полученным из JS вызова взаимодействия (дополнительные сведения см. в разделе ASP.NET Отрисовка основных Razor компонентов). Бесконечный цикл не создается, так как StateHasChanged вызывается только в том null
случаеscrollPosition
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
В предыдущем примере клиент загрязняет глобальную функцию. Более эффективный подход для приложений в рабочей среде приведен в разделе Изоляция JavaScript в модулях JavaScript.
Синхронная 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();
}
}
}
В предыдущем примере во время удаления модуля не застрелен, JSDisconnectedException так как BlazorSignalR нет канала в Blazor WebAssembly приложении, который теряется. Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).
Расположение JavaScript
Загрузите код JavaScript (JS) с помощью любого из подходов, описанных в статье о расположении JavaScript:
- Загрузите скрипт в разметку элемента
<head>
(обычно не рекомендуется). - Загрузите скрипт в разметку элемента
<body>
. - Загрузите скрипт из внешнего файла JavaScript (
.js
), размещенного совместно с компонентом - Загрузите скрипт из внешнего файла JavaScript (
.js
) - Внедрение скрипта до или после Blazor запуска
Сведения об изоляции скриптов в модулях 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 на стороне сервера вызовы взаимодействия не могут быть выданы после BlazorSignalR отключения канала. Без канала во время удаления компонентов или в любое другое время, когда канал не существует, следующий метод вызывает сбой и записывает сообщение о том, что канал отключен как:JSDisconnectedException
- JS Вызовы метода взаимодействия
Dispose
/DisposeAsync
вызовы для любого IJSObjectReference.
Чтобы избежать ведения журнала JSDisconnectedException или регистрировать пользовательские сведения на стороне Blazorсервера, перехватите исключение в инструкции try-catch
.
В следующем примере удаления компонентов:
- Серверный компонент реализует IAsyncDisposable.
module
IJSObjectReference— это модульJS.- JSDisconnectedException перехватываются и не регистрируются.
- Кроме того, вы можете записывать пользовательские сведения в инструкцию
catch
на любом выбранном уровне журнала. В следующем примере не регистрируются пользовательские сведения. В коде предполагается, что разработчик не заботится о том, когда или где каналы отключены во время удаления компонентов.
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (module is not null)
{
await module.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
Если необходимо очистить собственные JS объекты или выполнить другой JS код на клиенте после потери канала в серверном Blazor приложении, используйте MutationObserver
шаблон JS в клиенте. Шаблон MutationObserver
позволяет выполнять JS код при удалении элемента из DOM.
Дополнительные сведения см. в следующих статьях:
- ASP.NET взаимодействие с JavaScript Core Blazor (взаимодействие): включает пример
MutationObserver
кодаJS шаблона. - Обработка ошибок в приложениях ASP.NET CoreBlazor. В разделе взаимодействия JavaScript рассматривается обработка ошибок в JS сценариях взаимодействия.
- ASP.NET жизненный цикл основных компонентов: удаление компонентов с
IDisposable
IAsyncDisposable
разделом описывает реализацию шаблонов удаления в Razor компонентах.Razor
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 text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJs6.razor
:
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt() => result = await Prompt("Provide text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference module;
private string result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string> Prompt(string message)
{
return await module.InvokeAsync<string>("showPrompt", message);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
В предыдущем примере:
- По правилам идентификатор
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 и .jsMJS (документация по 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
) с помощью MyList
JS взаимодействия, так как Blazor взаимодействует с DOM для заполнения элементов списка этого элемента (<li>
) из Todos
объекта:
<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>
MyList
Использование ссылки на элемент для простого чтения содержимого DOM или активации события поддерживается.
Если JS взаимодействие мутирует содержимое элемента MyList
и Blazor пытается применить диффы к элементу, диффы не будут соответствовать DOM. Изменение содержимого списка с помощью взаимодействия со ссылкой на элемент не поддерживается.MyList
JS
Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).
ElementReference передается в код JS посредством взаимодействия с JS. Код HTMLElement
получает экземпляр JS, который может использоваться с обычными интерфейсами API DOM. Например, в приведенном ниже коде определяется метод расширения .NET (TriggerClickEvent
), который позволяет отправить щелчок мыши в элемент.
Функция JS clickElement
создает событие 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) =>
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/OnAfterRenderAsync.
- ElementReference имеет тип
struct
, который нельзя передать в качестве параметра компонента.
Чтобы сделать ссылку на элемент доступной для других компонентов, родительский компонент может:
- разрешить дочерним компонентам регистрировать обратные вызовы;
- вызывать зарегистрированные обратные вызовы во время события 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?" />
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)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJs7.razor.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJs7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
В предыдущем примере пространство имен приложения — 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=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
SurveyPrompt.razor.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
В предыдущем примере в качестве пространства имен приложения указано BlazorSample
с общими компонентами в папке Shared
. При локальном тестировании кода обновите пространство имен.
Повышение надежности вызовов взаимодействия с JavaScript
Этот раздел применяется только к компонентам интерактивного сервера, но клиентские компоненты также могут задавать JS время ожидания взаимодействия, если условия гарантируют это.
В серверных приложениях с интерактивностью сервера взаимодействие JavaScript (JS) может завершиться ошибкой из-за ошибок сети и должно рассматриваться как ненадежное. Blazor приложения используют одноминутное время ожидания для 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 вызовов взаимодействия для клиентских компонентов. Хотя канал не BlazorSignalR существует для клиентского компонента, 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);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
try
{
await mapInstance.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
if (mapModule is not null)
{
try
{
await mapModule.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJs8.razor
:
@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 8</PageTitle>
<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
try
{
await mapInstance.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
if (mapModule is not null)
{
try
{
await mapModule.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
В предыдущем примере создается интерактивный пользовательский интерфейс карты. Пользователь может выполнять следующие действия:
- прокручивать карту или изменять масштаб перетаскиванием;
- переходить к заданным расположениям нажатием кнопок.
В предыдущем примере:
- В случае с Blazor
<div>
с@ref="mapElement"
остается пустым. С помощью скриптаmapbox-gl.js
можно безопасно заполнять элемент и изменять его содержимое. Используйте этот метод с любой библиотекой JS, которая отрисовывает пользовательский интерфейс. В компоненты Razor можно даже внедрять компоненты из сторонней платформы одностраничных приложений JS, если они не пытаются изменить другие части страницы. Ситуация, когда внешний код JS изменяет элементы, которые Blazor не считает пустыми, небезопасна. - При использовании этого подхода необходимо учитывать то, как Blazor удерживает или уничтожает элементы DOM. Компонент безопасно обрабатывает события нажатия кнопки и обновляет существующий экземпляр карты, так как элементы DOM сохраняются по возможности. При отрисовке списка элементов карты из цикла
@foreach
необходимо использовать@key
, чтобы гарантировать сохранность экземпляров компонента. В противном случае изменения в данных списка могут приводить к нежелательному сохранению состояния предыдущих экземпляров компонента. Дополнительные сведения см. в статье об использовании атрибута@key
директивы для сохранения связи между элементами, компонентами и объектами модели. - Пример инкапсулирует JS логику и зависимости в модуле JavaScript и загружает модуль динамически с помощью идентификатора
import
. Дополнительные сведения см. в разделе Изоляция JavaScript в модулях JavaScript.
Поддержка массивов байтов
Blazor поддерживает оптимизированное взаимодействие с массивом байтов JavaScript (JS), которое позволяет избежать кодирования и декодирования массивов байтов в Base64. В следующем примере используется взаимодействие JS для передачи массива байтов в JavaScript.
Укажите функцию JS receiveByteArray
. Функция вызывается с помощью метода 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 ©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 ©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 (JS). Потоки создаются с помощью метода DotNetStreamReference.
DotNetStreamReference представляет поток .NET и использует следующие параметры:
stream
: поток, отправленный JSв .leaveOpen
: определяет, остается ли поток открытым после передачи. Если значение не указано, дляleaveOpen
по умолчанию задаетсяfalse
.
В JSэтом случае используйте буфер массива или доступный для чтения поток для получения данных:
Использование
ArrayBuffer
:async function streamToJavaScript(streamRef) { const data = await streamRef.arrayBuffer(); }
Использование
ReadableStream
:async function streamToJavaScript(streamRef) { const stream = await streamRef.stream(); }
В коде C#:
var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);
В предыдущем примере:
- Заполнитель
{STREAM}
представляет отправленный Stream объект JS. JS
является внедренным экземпляром IJSRuntime.
Удаление экземпляра DotNetStreamReference обычно не требуется. Если leaveOpen
задано значение false
по умолчанию, базовый объект Stream автоматически удаляется после передачи JS.
Если leaveOpen
есть true
, то удаление DotNetStreamReference не удаляет его базовый Streamобъект. Код приложения определяет, когда следует удалить базовый Streamобъект. При принятии решения о том, как удалить базовый Streamобъект, рассмотрите следующее:
- Stream Удаление времени передачи JS в приложение считается ошибкой приложения и может привести к возникновению необработанного исключения.
- Stream передача начинается сразу после DotNetStreamReference передачи в качестве аргумента JS вызову взаимодействия независимо от того, используется ли поток в JS логике.
Учитывая эти характеристики, рекомендуется удалить базовый Stream только после полного использования JS (обещание, возвращенное arrayBuffer
или stream
разрешено). Далее следует, что следует передать JS только в том случае, DotNetStreamReference если он безусловно будет использоваться логикойJS.
В статье Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core рассматривается обратная операция, потоковая передача данных из JavaScript в .NET.
Статья Загрузки файлов Blazor ASP.NET Core содержит сведения о скачивании файла в Blazor.
Перехват исключений JavaScript
Чтобы перехватить исключения JS, заключите взаимодействие JS в блок try
-catch
и перехватите JSException.
В следующем примере функция nonFunction
JS не существует. Если функция не найдена, объект 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}";
}
}
}
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.
Приведенный ниже компонент делает следующее.
- Вызывает функцию JS
longRunningFn
, если нажать кнопкуStart Task
. CancellationTokenSource используется для управления выполнением длительной функции. CancellationToken.Register задает делегат вызова взаимодействия JS для выполнения функции JSstopFn
при отмене CancellationTokenSource.Token. - Если нажать кнопку
Cancel Task
, CancellationTokenSource.Token отменяется путем вызова Cancel. - CancellationTokenSource удаляется в методе
Dispose
.
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 или более поздней версии.
Дополнительные сведения см. в статье JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.
Демаршалированные вызовы взаимодействия с JavaScript
Этот раздел относится к клиентским компонентам.
Немарсхоллированный взаимодействие с помощью IJSUnmarshalledRuntime интерфейса устарело и должно быть заменено взаимодействием JavaScript/[JSImport]
[JSExport]
.
Дополнительные сведения см. в статье JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.
Демаршалированные вызовы взаимодействия с 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 из .NET, как описано в этой статье, удалите все созданные/IJSInProcessObjectReference/
JSObjectReference
IJSObjectReferenceиз .NET или из JS нее JS памяти.При вызове .NET из JS, как описано в разделе "Вызов методов .NET из функций JavaScript в ASP.NET CoreBlazor", удалите созданный DotNetObjectReference из .NET или из JS нее утечку памяти .NET.
JS Ссылки на объекты взаимодействия реализуются в виде карты с ключом идентификатора на стороне JS вызова взаимодействия, создающего ссылку. Если удаление объектов инициируется из .NET или JS на стороне, удаляет запись из карты, Blazor а объект может быть собран мусором до тех пор, пока нет другой строгой ссылки на объект.
Как минимум, всегда удалять объекты, созданные на стороне .NET, чтобы избежать утечки управляемой памяти .NET.
Задачи очистки DOM во время удаления компонентов
Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).
Вызовы взаимодействия JavaScript без канала
Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).
Дополнительные ресурсы
- Вызов методов .NET из функций JavaScript в Blazor ASP.NET Core
InteropComponent.razor
пример (dotnet/AspNetCore
ветвь репозиторияmain
GitHub):main
ветвь представляет текущую разработку подразделения продукта для следующего выпуска ASP.NET Core. Чтобы выбрать ветвь для другого выпуска (например,release/{VERSION}
где{VERSION}
заполнитель является версией выпуска), используйте раскрывающийся список переключателей или тегов , чтобы выбрать ветвь. Для ветви, которая больше не существует, используйте вкладку "Теги ", чтобы найти API (например,v7.0.0
).- Blazorпримеры репозитория GitHub (
dotnet/blazor-samples
как скачать) - Обработка ошибок в приложениях ASP.NET Core Blazor (раздел взаимодействия JavaScript)
- Устранение угроз: функции JavaScript, вызываемые из .NET
ASP.NET Core