JavaScript-functies aanroepen vanuit .NET-methoden in ASP.NET Core Blazor
Notitie
Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.
Waarschuwing
Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.
Belangrijk
Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.
Zie de .NET 9-versie van dit artikelvoor de huidige release.
In dit artikel wordt uitgelegd hoe u JavaScript-functies (JS) aanroept vanuit .NET.
Zie Blazorvoor informatie over het aanroepen van .NET-methoden uit .
JS-functies aanroepen
IJSRuntime wordt geregistreerd door het Blazor framework. Als u JS wilt aanroepen vanuit .NET, injecteert u de IJSRuntime abstractie en roept u een van de volgende methoden aan:
Voor de voorgaande .NET-methoden die JS functies aanroepen:
- De functie-id (
String
) is relatief ten opzichte van het globale bereik (window
). Als uwindow.someScope.someFunction
wilt aanroepen, wordt de idsomeScope.someFunction
. U hoeft de functie niet te registreren voordat deze wordt aangeroepen. - Geef een willekeurig aantal JSON-serialiseerbare argumenten in
Object[]
door aan een JS-functie. - Met het annuleringstoken (
CancellationToken
) wordt een melding doorgegeven dat bewerkingen moeten worden geannuleerd. -
TimeSpan
vertegenwoordigt een tijdslimiet voor een JS bewerking. - Het
TValue
retourtype moet ook JSON serializeerbaar zijn.TValue
moet overeenkomen met het .NET-type dat het beste wordt toegewezen aan het geretourneerde JSON-type. - Er wordt een JS
Promise
geretourneerd voorInvokeAsync
methoden.InvokeAsync
pakt dePromise
uit en retourneert de waarde die door dePromise
wordt verwacht.
Voor Blazor apps waarvoor prerendering is ingeschakeld, wat de standaardinstelling is voor apps aan de serverzijde, is het aanroepen naar JS niet mogelijk tijdens het voorbereiden. Zie de sectie Prerendering voor meer informatie.
Het volgende voorbeeld is gebaseerd op TextDecoder
, een JS-decoder. In het voorbeeld ziet u hoe u een JS-functie aanroept vanuit een C#-methode waarmee een vereiste van ontwikkelaarscode naar een bestaande JS-API wordt offload. De functie JS accepteert een bytematrix van een C#-methode, decodeert de matrix en retourneert de tekst naar het onderdeel voor weergave.
<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
return decodedArray;
};
</script>
Het volgende onderdeel:
- Roept de functie
convertArray
JS aan met InvokeAsync bij het selecteren van een knop (Convert Array
). - Nadat de JS functie is aangeroepen, wordt de doorgegeven matrix geconverteerd naar een tekenreeks. De tekenreeks wordt geretourneerd naar het onderdeel voor weergave (
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));
}
}
JavaScript-API beperkt tot gebruikersbewegingen
Deze sectie is van toepassing op onderdelen aan de serverzijde.
Sommige JavaScript-API's (JS) van een browser kunnen alleen worden uitgevoerd in de context van een gebruikersbeweging, zoals het gebruik van de Fullscreen API
(MDN-documentatie). Deze API's kunnen niet worden aangeroepen via het JS interopmechanisme in onderdelen aan de serverzijde, omdat de verwerking van ui-gebeurtenissen asynchroon wordt uitgevoerd en over het algemeen niet meer in de context van de gebruikersbeweging. De app moet de UI-gebeurtenis volledig in JavaScript verwerken, dus gebruik onclick
in plaats van het Blazor-directivekenmerk van @onclick
.
JavaScript-functies aanroepen zonder een geretourneerde waarde te lezen (InvokeVoidAsync
)
Gebruik InvokeVoidAsync wanneer:
- .NET is niet vereist om het resultaat van een JavaScript-aanroep (JS) te lezen.
- JS functies retourneren void(0)/void 0 of niet-gedefinieerde.
Geef een displayTickerAlert1
JS-functie op. De functie wordt aangeroepen met InvokeVoidAsync en retourneert geen waarde:
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
Voorbeeld van onderdeel (.razor
) (InvokeVoidAsync
)
TickerChanged
roept de methode handleTickerChanged1
aan in het volgende onderdeel.
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);
}
}
Voorbeeld van klasse (.cs
) (InvokeVoidAsync
)
JsInteropClasses1.cs
:
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask TickerChanged(string symbol, decimal price) =>
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask TickerChanged(string symbol, decimal price) =>
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
roept de methode handleTickerChanged1
aan in het volgende onderdeel.
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-functies aanroepen en een geretourneerde waarde lezen (InvokeAsync
)
Gebruik InvokeAsync wanneer .NET het resultaat van een JavaScript-aanroep (JS) moet lezen.
Geef een displayTickerAlert2
JS-functie op. In het volgende voorbeeld wordt een tekenreeks geretourneerd voor weergave door de aanroeper:
<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>
Voorbeeld van onderdeel (.razor
) (InvokeAsync
)
TickerChanged
roept de methode handleTickerChanged2
aan en geeft de geretourneerde tekenreeks weer in het volgende onderdeel.
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}";
}
}
Voorbeeld van klasse (.cs
) (InvokeAsync
)
JsInteropClasses2.cs
:
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask<string> TickerChanged(string symbol, decimal price) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask<string> TickerChanged(string symbol, decimal price) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
roept de methode handleTickerChanged2
aan en geeft de geretourneerde tekenreeks weer in het volgende onderdeel.
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();
}
Scenario's voor het genereren van dynamische inhoud
Gebruik het kenmerk voor het genereren van dynamische inhoud met [Inject]
:
[Inject]
IJSRuntime JS { get; set; }
Vooraf Renderen
Deze sectie heeft betrekking op serverside-apps die Razor-componenten preregeneren. Prerendering wordt behandeld in Prerender ASP.NET Core Razor onderdelen.
Notitie
Interne navigatie voor interactieve routering in Blazor Web App-systemen hoeft geen nieuwe pagina-inhoud van de server aan te vragen. Daarom vindt het prerendering niet plaats voor interne paginaaanvragen. Als de app gebruikmaakt van interactieve routering, herlaad dan de hele pagina voor voorbeelden die het prerenderingsgedrag demonstreren. Zie Prerender ASP.NET Core Razor-onderdelenvoor meer informatie.
Deze sectie is van toepassing op server-side apps en gehoste Blazor WebAssembly-apps die Razor-onderdelen vooraf renderen. Prerendering wordt behandeld in Het integreren van ASP.NET Core Razor-componenten met MVC of Razor-pagina's.
Tijdens het prerenderen is het aanroepen van JavaScript (JS) niet mogelijk. In het volgende voorbeeld wordt gedemonstreerd hoe u JS interop kunt gebruiken als onderdeel van de initialisatielogica van een component op een manier die compatibel is met voorrendering.
De volgende scrollElementIntoView
functie:
- Scrollt naar het doorgegeven element met
scrollIntoView
. - Retourneert de
top
eigenschapswaarde van het element uit de methodegetBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Wanneer IJSRuntime.InvokeAsync de functie JS aanroept in onderdeelcode, wordt de ElementReference alleen gebruikt in OnAfterRenderAsync en niet in een eerdere levenscyclusmethode omdat er geen HTML DOM-element is totdat het onderdeel is weergegeven.
StateHasChanged
(verwijzingsbron) wordt aangeroepen om de her-rendering van de component in de wachtrij te plaatsen met de nieuwe toestand verkregen uit de JS interop-aanroep (voor meer informatie, zie ASP.NET Core Razor component rendering). Er wordt geen oneindige lus gemaakt omdat StateHasChanged alleen wordt aangeroepen wanneer scrollPosition
wordt null
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
Het voorgaande voorbeeld vervuilt de klant met een globale functie. Zie JavaScript-isolatie in JavaScript-modulesvoor een betere benadering van productie-apps.
Synchrone JS interoperabiliteit in clientonderdelen
Deze sectie is alleen van toepassing op onderdelen aan de clientzijde.
JS interop-aanroepen asynchroon zijn, ongeacht of de aangeroepen code synchroon of asynchroon is. Aanroepen zijn asynchroon om ervoor te zorgen dat onderdelen compatibel zijn met de rendermodi aan de serverzijde en clientzijde. Op de server moeten alle JS interop-aanroepen asynchroon zijn omdat ze via een netwerkverbinding worden verzonden.
Als u zeker weet dat uw onderdeel alleen wordt uitgevoerd op WebAssembly, kunt u ervoor kiezen om synchrone JS interop-aanroepen uit te voeren. Dit heeft iets minder overhead dan het maken van asynchrone aanroepen en kan leiden tot minder rendercycli, omdat er geen tussenliggende status is tijdens het wachten op resultaten.
Als u een synchrone aanroep wilt maken van .NET naar JavaScript in een onderdeel aan de clientzijde, cast IJSRuntime naar IJSInProcessRuntime om de JS interop-aanroep te maken:
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
Wanneer u met IJSObjectReference werkt in ASP.NET Core 5.0- of hoger-onderdelen aan de clientzijde, kunt u in plaats daarvan IJSInProcessObjectReference synchroon gebruiken. IJSInProcessObjectReference implementeert IAsyncDisposable/IDisposable en moet worden verwijderd voor garbagecollection om een geheugenlek te voorkomen, zoals in het volgende voorbeeld wordt gedemonstreerd:
@inject IJSRuntime JS
@implements IDisposable
...
@code {
...
private IJSInProcessObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var jsInProcess = (IJSInProcessRuntime)JS;
module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import",
"./scripts.js");
var value = module.Invoke<string>("javascriptFunctionIdentifier");
}
}
...
void IDisposable.Dispose()
{
if (module is not null)
{
await module.Dispose();
}
}
}
In het voorgaande voorbeeld wordt een JSDisconnectedException niet vastgelopen tijdens het verwijderen van de module omdat er geen Blazoris:SignalR circuit in een Blazor WebAssembly-app die verloren gaat. Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.
JavaScript-locatie
JavaScript-code (JS) laden met behulp van een van de methoden die worden beschreven in het artikel over javaScript-locatie:
-
een script insluiten in
<head>
markup (over het algemeen niet aanbevolen) -
Laad een script in
<body>
-
een script laden vanuit een extern JavaScript-bestand (
.js
) dat is gekoppeld aan een onderdeel -
een script laden vanuit een extern JavaScript-bestand (
.js
) - Een script injecteren vóór of nadat Blazor start
Waarschuwing
Plaats alleen een <script>
tag in een onderdeelbestand (.razor
) als het onderdeel gegarandeerd statische SSR-rendering (statische SSR) omdat de <script>
tag niet dynamisch kan worden bijgewerkt.
Waarschuwing
Plaats geen <script>
tag in een onderdeelbestand (.razor
) omdat de tag <script>
niet dynamisch kan worden bijgewerkt.
JavaScript-isolatie in JavaScript-modules
Blazor maakt isolatie van JavaScript (JS) mogelijk in standaard JavaScript-modules (ECMAScript-specificatie). Laden van JavaScript-modules werkt op dezelfde manier in Blazor als voor andere typen web-apps en u kunt aanpassen hoe modules in uw app worden gedefinieerd. Zie MDN-webdocumenten: JavaScript-modulesvoor een handleiding over het gebruik van JavaScript-modules.
JS isolatie biedt de volgende voordelen:
- Geïmporteerde JS komt niet meer voor in de globale naamruimte.
- Gebruikers van een bibliotheek en onderdelen hoeven de gerelateerde JSniet te importeren.
Dynamisch importeren met de import()
-operator wordt ondersteund met ASP.NET Core en Blazor:
if ({CONDITION}) import("/additionalModule.js");
In het voorgaande voorbeeld vertegenwoordigt de tijdelijke aanduiding {CONDITION}
een voorwaardelijke controle om te bepalen of de module moet worden geladen.
Zie Kan ik gebruiken: JavaScript-modules: dynamische importvoor browsercompatibiliteit.
In scenario's aan de serverzijde kan JS interop-aanroepen niet worden uitgegeven, nadat Blazor's SignalR-circuit is verbroken. Wanneer er geen circuit is tijdens het verwijderen van onderdelen of op enig ander moment dat er geen circuit is, falen de volgende methodes en wordt er een melding gelogd dat het circuit is verbroken als een JSDisconnectedException.
- JS interop-methode-aanroepen
-
Dispose
/DisposeAsync
roept alle IJSObjectReferenceaan.
Om het voorkomen van logboekregistratie JSDisconnectedException of aangepaste gegevens aan de serverzijde in Blazorte registreren, vang de uitzondering op in een try-catch
-instructie.
Voor het volgende voorbeeld van het verwijderen van onderdelen:
- Het onderdeel aan de serverzijde implementeert IAsyncDisposable.
-
module
is een IJSObjectReference voor een JS module. - JSDisconnectedException wordt gevangen en niet geregistreerd.
- Desgewenst kunt u aangepaste gegevens in de
catch
-instructie vastleggen op het gewenste logboekniveau. In het volgende voorbeeld worden geen aangepaste gegevens vastgelegd. In de code wordt ervan uitgegaan dat de ontwikkelaar er niet om geeft wanneer of waar circuits worden losgekoppeld tijdens het verwijderen van onderdelen.
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (module is not null)
{
await module.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
Als u uw eigen JS objecten moet opschonen of andere JS code op de client moet uitvoeren nadat een circuit in een Blazor-app aan de serverzijde verloren is gegaan, gebruikt u het MutationObserver
patroon in JS op de client. Met het MutationObserver
patroon kunt u JS code uitvoeren wanneer een element uit de DOM wordt verwijderd.
Zie de volgende artikelen voor meer informatie:
-
ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop): bevat een codevoorbeeld van het
MutationObserver
-patroon. - Afhandelen van fouten in ASP.NET Core Blazor-apps: in de sectie JavaScript-interop wordt de foutafhandeling in JS interop-scenario's besproken.
-
ASP.NET levenscyclus van kernonderdelen Razor: In de sectie Component verwijderen met
IDisposable
enIAsyncDisposable
wordt beschreven hoe u verwijderingspatronen in Razor onderdelen implementeert.
Met de volgende JS module wordt een JS-functie geëxporteerd voor het weergeven van een browservensterprompt. Plaats de volgende JS code in een extern JS bestand.
wwwroot/scripts.js
:
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}
Voeg de voorgaande JS module toe aan een app of klassebibliotheek als een statische webasset in de map wwwroot
en importeer de module vervolgens in de .NET-code door InvokeAsync aan te roepen op het IJSRuntime exemplaar.
IJSRuntime importeert de module als een IJSObjectReference, die een verwijzing naar een JS-object uit .NET-code vertegenwoordigt. Gebruik de IJSObjectReference om geëxporteerde JS functies uit de module aan te roepen.
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();
}
}
}
In het voorgaande voorbeeld:
- Volgens de conventie is de
import
-id een speciale id die specifiek wordt gebruikt voor het importeren van een JS-module. - Geef het externe JS-bestand van de module op met behulp van het stabiele statische webassetpad:
./{SCRIPT PATH AND FILE NAME (.js)}
, waarbij:- Het padsegment voor de huidige map (
./
) is vereist om het juiste statische assetpad naar het JS-bestand te maken. - De tijdelijke aanduiding
{SCRIPT PATH AND FILE NAME (.js)}
is het pad en de bestandsnaam onderwwwroot
.
- Het padsegment voor de huidige map (
- Hiermee wordt de IJSObjectReference voor garbagecollection in IAsyncDisposable.DisposeAsyncverwijderd.
- Plaats geen
<script>
tag voor het script na het Blazor script omdat de module automatisch wordt geladen en in de cache wordt opgeslagen wanneer de dynamischeimport()
wordt aangeroepen.
Dynamisch importeren van een module vereist een netwerkaanvraag, zodat deze alleen asynchroon kan worden bereikt door InvokeAsyncaan te roepen.
IJSInProcessObjectReference
vertegenwoordigt een verwijzing naar een JS-object waarvan functies synchroon kunnen worden aangeroepen in onderdelen aan de clientzijde. Zie de synchrone JS interoperabiliteit in onderdelen aan de clientzijde voor meer informatie.
Notitie
Wanneer het externe JS-bestand wordt geleverd door een Razor klassebibliotheek, geeft u het JS-bestand van de module op met behulp van het stabiele statische webassetpad: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}
:
- Het padsegment voor de huidige map (
./
) is vereist om het juiste statische assetpad naar het JS-bestand te maken. - De tijdelijke aanduiding
{PACKAGE ID}
is de pakket-id van de bibliotheek. De pakket-id wordt standaard ingesteld op de assemblynaam van het project als<PackageId>
niet is opgegeven in het projectbestand. In het volgende voorbeeld is de assembly-naam van de bibliotheekComponentLibrary
en wordt in het projectbestand van de bibliotheek geen<PackageId>
opgegeven. - De tijdelijke aanduiding
{SCRIPT PATH AND FILE NAME (.js)}
is het pad en de bestandsnaam onderwwwroot
. In het volgende voorbeeld wordt het externe JS-bestand (script.js
) in dewwwroot
map van de klassebibliotheek geplaatst. -
module
is een privé nullable IJSObjectReference van de componentklasse (private IJSObjectReference? module;
).
module = await js.InvokeAsync<IJSObjectReference>(
"import", "./_content/ComponentLibrary/scripts.js");
Zie ASP.NET Core Razor-onderdelen van een Razor klassebibliotheek (RCL) gebruikenvoor meer informatie.
In de Blazor documentatie gebruiken voorbeelden de .js
bestandsextensie voor modulebestanden, niet de nieuwere .mjs
bestandsextensie (RFC 9239). Onze documentatie blijft de .js
bestandsextensie gebruiken om dezelfde redenen waarom de documentatie van de Mozilla Foundation de .js
bestandsextensie blijft gebruiken. Zie Aside - .mjs versus .js (MDN-documentatie)voor meer informatie.
Verwijzingen naar elementen vastleggen
Voor sommige interopscenario's voor JavaScript (JS) zijn verwijzingen naar HTML-elementen vereist. Een UI-bibliotheek kan bijvoorbeeld een elementverwijzing vereisen voor initialisatie of u moet mogelijk opdrachtachtige API's aanroepen voor een element, zoals click
of play
.
Noteer verwijzingen naar HTML-elementen in een onderdeel met behulp van de volgende aanpak:
- Voeg een
@ref
kenmerk toe aan het HTML-element. - Definieer een veld van het type ElementReference waarvan de naam overeenkomt met de waarde van het kenmerk
@ref
.
In het volgende voorbeeld ziet u hoe u een verwijzing naar het username
<input>
-element vastlegt:
<input @ref="username" ... />
@code {
private ElementReference username;
}
Waarschuwing
Gebruik alleen een elementreferentie om de inhoud van een leeg element te wijzigen dat niet met Blazorinterageert. Dit scenario is handig wanneer een API van derden inhoud aan het element levert. Omdat Blazor geen interactie heeft met het element, is er geen sprake van een conflict tussen Blazorweergave van het element en de DOM.
In het volgende voorbeeld is het gevaarlijk om de inhoud van de niet-geordende lijst (ul
) te muteren met behulp van MyList
via JS interop, omdat Blazor communiceert met de DOM om de lijstitems (<li>
) van het Todos
-object te vullen:
<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>
Het gebruik van de MyList
elementreferentie voor het lezen van DOM-inhoud of het activeren van een gebeurtenis wordt ondersteund.
Als JSde inhoud van element MyList
muteert en Blazor probeert diffs toe te passen op het element, zullen de diffs niet overeenkomen met de DOM. Het wijzigen van de inhoud van de lijst via JS interop met de MyList
elementverwijzing wordt niet ondersteund.
Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.
Een ElementReference wordt via JS interop doorgegeven aan JS code. De JS-code ontvangt een HTMLElement
exemplaar, dat kan worden gebruikt met normale DOM-API's. Met de volgende code wordt bijvoorbeeld een .NET-extensiemethode (TriggerClickEvent
) gedefinieerd waarmee een muisklik naar een element kan worden verzonden.
De JS-functie clickElement
maakt een click
gebeurtenis op het doorgegeven HTML-element (element
):
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}
Gebruik JSom een JSRuntimeExtensions.InvokeVoidAsync-functie aan te roepen die geen waarde retourneert. Met de volgende code wordt een click
-gebeurtenis aan de clientzijde geactiveerd door de eerder aangeroepen JS-functie met de vastgelegde ElementReferencete gebruiken:
@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);
}
}
Als u een extensiemethode wilt gebruiken, maakt u een statische extensiemethode die het IJSRuntime-exemplaar ontvangt:
public static async Task TriggerClickEvent(this ElementReference elementRef,
IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}
De methode clickElement
wordt rechtstreeks aangeroepen op het object. In het volgende voorbeeld wordt ervan uitgegaan dat de TriggerClickEvent
methode beschikbaar is in de JsInteropClasses
naamruimte:
@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);
}
}
Belangrijk
De exampleButton
variabele wordt alleen ingevuld nadat het onderdeel is weergegeven. Als een niet-ingevulde ElementReference wordt doorgegeven aan JS code, ontvangt de JS code een waarde van null
. Als u elementverwijzingen wilt bewerken nadat het onderdeel klaar is met renderen, gebruikt u de levenscyclusmethoden voor OnAfterRenderAsync
of OnAfterRender
van onderdelen.
Wanneer u met algemene typen werkt en een waarde retourneert, gebruikt u ValueTask<TResult>:
public static ValueTask<T> GenericMethod<T>(
this ElementReference elementRef, IJSRuntime js) =>
js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
De tijdelijke aanduiding {JAVASCRIPT FUNCTION}
is de JS functie-id.
GenericMethod
wordt rechtstreeks op het object aangeroepen met een type. In het volgende voorbeeld wordt ervan uitgegaan dat de GenericMethod
beschikbaar is in de JsInteropClasses
naamruimte:
@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);
}
}
Verwijzingselementen voor onderdelen
Een ElementReference kan niet worden doorgegeven tussen onderdelen omdat:
- Het exemplaar blijft alleen bestaan nadat het onderdeel is weergegeven. Dit is tijdens of nadat de OnAfterRender/OnAfterRenderAsync methode van een onderdeel wordt uitgevoerd.
- Een ElementReference is een
struct
, die niet kan worden doorgegeven als een componentparameter.
Als een bovenliggend onderdeel een elementverwijzing beschikbaar wil maken voor andere onderdelen, kan het volgende doen:
- Sta toe dat onderliggende onderdelen callbacks registreren.
- Roep de geregistreerde callbacks aan tijdens de OnAfterRender gebeurtenis met de doorgegeven elementreferentie. Indirect kunnen onderliggende onderdelen met de elementreferentie van het bovenliggende element communiceren.
<style>
.red { color: red }
</style>
<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>
CallJs7.razor
(bovenliggend onderdeel):
@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
(bovenliggend onderdeel):
@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
(bovenliggend onderdeel):
@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
(bovenliggend onderdeel):
@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
(bovenliggend onderdeel):
@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
(bovenliggend onderdeel):
@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);
}
}
}
}
In het voorgaande voorbeeld is de namespace van de app BlazorSample
. Als u de code lokaal test, werkt u de naamruimte bij.
SurveyPrompt.razor
(onderliggend onderdeel):
<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();
}
}
}
In het voorgaande voorbeeld is de naamruimte van de app BlazorSample
en bevinden de gedeelde onderdelen zich in de map Shared
. Als u de code lokaal test, werkt u de naamruimte bij.
Versterk JavaScript-interoperabiliteitsaanroepen
Deze sectie is alleen van toepassing op interactieve serveronderdelen, maar onderdelen aan de clientzijde kunnen ook JS time-outs voor interop instellen als deze voorwaarden rechtvaardigen.
In apps aan de serverzijde met server-interactiviteit kan JavaScript (JS) interoperabiliteit mislukken vanwege netwerkfouten en moet dit worden beschouwd als onbetrouwbaar. Apps van Blazor gebruiken een time-out van één minuut voor interop-aanroepen van JS. Als een app een agressievere time-out kan verdragen, stelt u de time-out in met een van de volgende methoden.
Een globale time-out instellen in de Program.cs
met CircuitOptions.JSInteropDefaultCallTimeout:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});
builder.Services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
Stel een globale time-out in de Startup.ConfigureServices
methode van Startup.cs
in met CircuitOptions.JSInteropDefaultCallTimeout:
services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
De tijdelijke aanduiding {TIMEOUT}
is een TimeSpan (bijvoorbeeld TimeSpan.FromSeconds(80)
).
Stel een time-out per aanroep in in onderdeelcode. De opgegeven time-out overschrijft de globale time-out die is ingesteld door JSInteropDefaultCallTimeout:
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, [ "Arg1" ]);
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });
In het voorgaande voorbeeld:
- De tijdelijke aanduiding
{TIMEOUT}
is een TimeSpan (bijvoorbeeldTimeSpan.FromSeconds(80)
). - De placeholder
{ID}
is de identifier voor de functie die moet worden aangeroepen. De waardesomeScope.someFunction
bijvoorbeeld de functiewindow.someScope.someFunction
aanroept.
Hoewel een veelvoorkomende oorzaak van JS interop-aanroepen netwerkfouten bij serveronderdelen zijn, kunnen time-outs per aanroep worden ingesteld voor JS interop-aanroepen voor onderdelen aan de clientzijde. Hoewel er geen Blazor-SignalR circuit bestaat voor een onderdeel aan de clientzijde, kunnen JS interop-aanroepen mislukken om andere redenen die van toepassing zijn.
Zie Richtlijnen voor het beperken van bedreigingen voor ASP.NET Core Blazor interactieve rendering aan de serverzijdevoor meer informatie over uitputting van resources.
Vermijd kringverwijzingen naar objecten
Objecten die kringverwijzingen bevatten, kunnen niet op de client worden geserialiseerd voor:
- Aanroepen van .NET-methoden.
- JavaScript-methode aanroepen vanuit C# wanneer het retourtype kringverwijzingen bevat.
JavaScript-bibliotheken die de gebruikersinterface weergeven
Soms wilt u mogelijk JavaScript-bibliotheken (JS) gebruiken die zichtbare elementen van de gebruikersinterface in de browser-DOM produceren. Op het eerste gezicht lijkt dit misschien moeilijk omdat het diffing-systeem van Blazorafhankelijk is van controle over de structuur van DOM-elementen en fouten ondervindt als sommige externe code de DOM-structuur muteert en het mechanisme voor het toepassen van diffs ongeldig maakt. Dit is geen Blazor-specifieke beperking. Dezelfde uitdaging treedt op bij elk op diff gebaseerd UI-framework.
Gelukkig is het eenvoudig om een extern gegenereerde gebruikersinterface betrouwbaar in te sluiten binnen een Razor-component interface. De aanbevolen techniek is om de code van het onderdeel (.razor
bestand) een leeg element te laten produceren. Wat het diffingsysteem van Blazorbetreft, wordt het element altijd als leeg beschouwd, zodat de renderer niet in het element recurst en de inhoud ongemoeid laat. Hierdoor is het veilig om het element te vullen met willekeurige extern beheerde inhoud.
In het volgende voorbeeld ziet u het concept. In de if
-verklaring, wanneer firstRender
is true
, communiceert u met unmanagedElement
buiten Blazor met behulp van JS interop. Roep bijvoorbeeld een externe JS-bibliotheek aan om het element te vullen.
Blazor laat de inhoud van het element alleen staan totdat dit onderdeel wordt verwijderd. Wanneer het onderdeel wordt verwijderd, wordt ook de volledige DOM-substructuur van het onderdeel verwijderd.
<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)
{
...
}
}
}
Bekijk het volgende voorbeeld waarmee een interactieve kaart wordt weergegeven met behulp van opensource Mapbox-API's.
De volgende JS module wordt in de app geplaatst of beschikbaar gesteld vanuit een Razor klassebibliotheek.
Notitie
Als u de Mapbox--kaart wilt maken, haalt u een toegangstoken op van de inlogpagina van Mapbox en plaats het waar {ACCESS TOKEN}
verschijnt in de volgende code.
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]);
}
Als u de juiste stijl wilt produceren, voegt u de volgende stylesheet-tag toe aan de HTML-hostpagina.
Voeg het volgende <link>
-element toe aan de <head>
-elementmarkering (locatie van <head>
-inhoud):
<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();
}
}
}
In het voorgaande voorbeeld wordt een interactieve gebruikersinterface voor kaarten geproduceerd. De gebruiker:
- Kan slepen om te scrollen of in te zoomen.
- Selecteer knoppen om naar vooraf gedefinieerde locaties te gaan.
In het voorgaande voorbeeld:
- De
<div>
met@ref="mapElement"
blijft leeg wat betreft Blazor. Hetmapbox-gl.js
script kan het element veilig vullen en de inhoud ervan wijzigen. Gebruik deze techniek met elke JS-bibliotheek die de gebruikersinterface weergeeft. U kunt componenten van een extern JS SPA-framework inbedden binnen Razor componenten, zolang ze niet proberen andere delen van de pagina te benaderen en aan te passen. Het is niet veilig voor externe JS code om elementen te wijzigen die Blazor niet als leeg beschouwt. - Houd bij het gebruik van deze methode rekening met de regels over hoe Blazor DOM-elementen bewaart of vernietigt. Het onderdeel verwerkt veilig klikgebeurtenissen en werkt het bestaande kaartexemplaar bij, omdat DOM-elementen zoveel mogelijk behouden blijven. Als u een lijst met kaartelementen vanuit een
@foreach
lus weergeeft, wilt u@key
gebruiken om het behoud van onderdeelexemplaren te garanderen. Anders kunnen wijzigingen in de lijstgegevens ertoe leiden dat onderdeelexemplaren de status van eerdere exemplaren op een ongewenste manier behouden. Zie voor meer informatie hoe u het instructiekenmerk@key
gebruikt om de relatie tussen elementen, onderdelen en modelobjectente behouden. - In het voorbeeld worden JS logica en afhankelijkheden in een JavaScript-module ingekapseld en wordt de module dynamisch geladen met behulp van de
import
-id. Zie JavaScript-isolatie in JavaScript-modulesvoor meer informatie.
Ondersteuning voor bytematrix
Blazor ondersteunt geoptimaliseerde byte-array JavaScript-interoperabiliteit (JS) die het encoderen/decoderen van byte-arrays naar Base64 vermijdt. In het volgende voorbeeld wordt JS interop gebruikt om een bytematrix door te geven aan JavaScript.
Geef een receiveByteArray
JS-functie op. De functie wordt aangeroepen met InvokeVoidAsync en retourneert geen waarde:
<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>
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);
}
}
Zie .NET-methoden aanroepen vanuit JavaScript-functies in ASP.NET Core Blazorvoor meer informatie over het gebruik van een bytematrix bij het aanroepen van .NET vanuit JavaScript.
Streamen van .NET naar JavaScript
Blazor biedt ondersteuning voor het rechtstreeks streamen van gegevens van .NET naar JavaScript (JS). Streams worden gemaakt met behulp van een DotNetStreamReference.
DotNetStreamReference vertegenwoordigt een .NET-stream en gebruikt de volgende parameters:
-
stream
: de stroom die naar JSis verzonden. -
leaveOpen
: bepaalt of de stroom open blijft na verzending. Als er geen waarde is opgegeven, wordtleaveOpen
standaard ingesteld opfalse
.
Gebruik in JSeen matrixbuffer of een leesbare stroom om de gegevens te ontvangen:
Met behulp van een
ArrayBuffer
:async function streamToJavaScript(streamRef) { const data = await streamRef.arrayBuffer(); }
Een
ReadableStream
gebruiken:async function streamToJavaScript(streamRef) { const stream = await streamRef.stream(); }
In C#-code:
var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);
In het voorgaande voorbeeld:
- De tijdelijke aanduiding
{STREAM}
vertegenwoordigt de Stream die naar JSis verzonden. -
JS
is een geïnjecteerd IJSRuntime exemplaar.
Het verwijderen van een DotNetStreamReference exemplaar is meestal niet nodig. Wanneer leaveOpen
is ingesteld op de standaardwaarde van false
, wordt de onderliggende Stream automatisch verwijderd na verzending naar JS.
Als leaveOpen
gelijk is aan true
, dan wordt bij het verwijderen van een DotNetStreamReference de onderliggende Streamniet verwijderd. De code van de app bepaalt wanneer de onderliggende Streammoet worden verwijderd. Houd rekening met het volgende wanneer u besluit hoe u de onderliggende Streammoet verwijderen:
- Het verwijderen van een Stream terwijl deze naar JS wordt verzonden, wordt beschouwd als een toepassingsfout en kan een niet-verwerkte uitzondering veroorzaken.
- De Stream overdracht begint zodra de DotNetStreamReference wordt doorgegeven als een argument voor een JS interop-aanroep, ongeacht of de stroom daadwerkelijk in de JS logica wordt gebruikt.
Gezien deze kenmerken raden we aan om de onderliggende Stream pas te verwijderen nadat deze volledig is verbruikt door JS (de belofte die wordt geretourneerd door arrayBuffer
of stream
wordt opgelost). Het volgt dat een DotNetStreamReference alleen moet worden doorgegeven aan JS als deze onvoorwaardelijk wordt gebruikt door JS-logica.
.NET-methoden aanroepen vanuit JavaScript-functies in ASP.NET Core Blazor de omgekeerde bewerking omvat, streamen van JavaScript naar .NET.
ASP.NET Core Blazor bestand downloads behandelt hoe u een bestand kunt downloaden in Blazor.
JavaScript-uitzonderingen vangen
Als u JS uitzonderingen wilt ondervangen, wikkelt u de JS interop in een try
-catch
-blok en vangt u een JSExceptionop.
In het volgende voorbeeld bestaat de functie nonFunction
JS niet. Wanneer de functie niet wordt gevonden, wordt de JSException vastgelopen met een Message die de volgende fout aangeeft:
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}";
}
}
}
Een langlopende JavaScript-functie afbreken
Gebruik een JS AbortController met een CancellationTokenSource in de component om een langlopende JavaScript-functie af te breken vanuit C#-code.
De volgende JSHelpers
klasse bevat een gesimuleerde langlopende functie, longRunningFn
, om continu te tellen totdat de AbortController.signal
aangeeft dat AbortController.abort
is aangeroepen. De sleep
-functie is bedoeld voor demonstratiedoeleinden om trage uitvoering van de langlopende functie te simuleren en niet aanwezig te zijn in productiecode. Wanneer een component stopFn
aanroept, wordt aangegeven dat de longRunningFn
moet worden afgebroken via de voorwaardelijke controle van de while
-lus op 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>
Het volgende onderdeel:
- Roept de JS functie aan
longRunningFn
wanneer de knopStart Task
is geselecteerd. Een CancellationTokenSource wordt gebruikt om de uitvoering van de langlopende functie te beheren. CancellationToken.Register stelt een JS aanroepdeleger in om de JS functie uit te voerenstopFn
wanneer de CancellationTokenSource.Token wordt geannuleerd. - Wanneer de knop
Cancel Task
is geselecteerd, wordt de CancellationTokenSource.Token geannuleerd met een aanroep naar Cancel. - De CancellationTokenSource wordt verwijderd volgens de methode
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();
}
}
De ontwikkelhulpprogramma's van een browser console geeft de uitvoering van de langlopende JS-functie aan nadat de knop Start Task
is geselecteerd en wanneer de functie is afgebroken nadat de Cancel Task
knop is geselecteerd:
longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!
JavaScript [JSImport]
/[JSExport]
interop
Deze sectie is van toepassing op onderdelen aan de clientzijde.
Als alternatief voor interactie met JavaScript (JS) in clientonderdelen met behulp van BlazorJS interop-mechanisme op basis van de IJSRuntime interface, is een JS[JSImport]
/[JSExport]
interop-API beschikbaar voor apps die gericht zijn op .NET 7 of hoger.
Zie JavaScript JSImport/JSExport-interop met ASP.NET Core Blazorvoor meer informatie.
Niet-gemarkeerde JavaScript-interop
Deze sectie is van toepassing op onderdelen aan de clientzijde.
Ongemarshalled interop gebruikmakend van de IJSUnmarshalledRuntime-interface is verouderd en moet worden vervangen door JavaScript [JSImport]
/[JSExport]
interop.
Zie JavaScript JSImport/JSExport-interop met ASP.NET Core Blazorvoor meer informatie.
Niet-gemarkeerde JavaScript-interop
Blazor WebAssembly onderdelen kunnen slechte prestaties ondervinden wanneer .NET-objecten worden geserialiseerd voor JavaScript -interop (JS) en een van de volgende waar zijn:
- Een groot aantal .NET-objecten wordt snel geserialiseerd. Slechte prestaties kunnen er bijvoorbeeld toe leiden wanneer JS interoperabiliteitsaanroepen worden uitgevoerd op basis van het verplaatsen van een invoerapparaat, zoals het draaien van een muiswiel.
- Grote .NET-objecten of veel .NET-objecten moeten worden geserialiseerd voor JS interop. Slechte prestaties kunnen er bijvoorbeeld toe leiden dat JS interop-aanroepen tientallen bestanden serialiseren.
IJSUnmarshalledObjectReference vertegenwoordigt een verwijzing naar een JS-object waarvan functies kunnen worden aangeroepen zonder de overhead van het serialiseren van .NET-gegevens.
In het volgende voorbeeld:
- Een struct- met een tekenreeks en een geheel getal wordt niet-geserialiseerd doorgegeven aan JS.
- JS functies verwerken de gegevens en retourneren een Booleaanse waarde of tekenreeks naar de aanroeper.
- Een JS tekenreeks wordt niet rechtstreeks omgezet in een .NET-
string
-object. De functieunmarshalledFunctionReturnString
roeptBINDING.js_string_to_mono_string
aan om de conversie van een JS tekenreeks te beheren.
Notitie
De volgende voorbeelden zijn geen typische use cases voor dit scenario, omdat de struct doorgegeven aan JS niet resulteert in slechte onderdelenprestaties. In het voorbeeld wordt slechts een klein object gebruikt om de concepten te demonstreren voor het doorgeven van niet-geserialiseerde .NET-gegevens.
<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>
Waarschuwing
De js_string_to_mono_string
functienaam, gedrag en bestaan zijn onderhevig aan wijzigingen in een toekomstige release van .NET. Bijvoorbeeld:
- De naam van de functie wordt waarschijnlijk gewijzigd.
- De functie zelf kan worden verwijderd ten gunste van automatische conversie van tekenreeksen door het framework.
CallJsExample10.razor
:
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string? callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
Als een IJSUnmarshalledObjectReference exemplaar niet wordt verwijderd in C#-code, kan deze worden verwijderd in JS. Met de volgende dispose
functie wordt de objectverwijzing verwijderd wanneer deze wordt aangeroepen vanuit JS:
window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
return {
dispose: function () {
DotNet.disposeJSObjectReference(this);
},
...
};
}
Matrixtypen kunnen worden geconverteerd van JS objecten naar .NET-objecten met behulp van js_typed_array_to_array
, maar de JS matrix moet een getypte matrix zijn. Matrices uit JS kunnen in C#-code worden gelezen als een .NET-objectmatrix (object[]
).
Andere gegevenstypen, zoals tekenreeksmatrices, kunnen worden geconverteerd, maar vereisen dat u een nieuw Mono-matrixobject (mono_obj_array_new
) maakt en de waarde ervan instelt (mono_obj_array_set
).
Waarschuwing
JS functies die worden geleverd door het Blazor-framework, zoals js_typed_array_to_array
, mono_obj_array_new
en mono_obj_array_set
, zijn onderhevig aan naamwijzigingen, gedragswijzigingen of verwijdering in toekomstige versies van .NET.
Verwijdering van JavaScript-interoperabiliteitsobjectverwijzingen
Voorbeelden in de Interop-artikelen van JavaScript (JS) laten typische patronen voor objectverwijdering zien:
Wanneer u JS aanroept vanuit .NET, zoals beschreven in dit artikel, verwijdert u alle gemaakte IJSObjectReference/IJSInProcessObjectReference/
JSObjectReference
van .NET of van JS om te voorkomen dat JS geheugen wordt gelekt.Wanneer u .NET aanroept vanuit JS, zoals beschreven in .NET-methoden aanroepen vanuit JavaScript-functies in ASP.NET Core-Blazor, verwijdert u een gemaakte DotNetObjectReference van .NET of van JS om te voorkomen dat .NET-geheugen wordt gelekt.
JS interop-objectverwijzingen worden geïmplementeerd als een kaart die is gekoppeld aan een id aan de zijkant van de JS interop-aanroep waarmee de verwijzing wordt gemaakt. Wanneer objectverwijdering wordt geïnitieerd vanaf de .NET- of JS-kant, verwijdert Blazor de vermelding uit de kaart en kan het object worden verzameld, zolang er geen andere sterke koppeling naar het object bestaat.
Verwijder minimaal objecten die aan de .NET-zijde zijn gemaakt om te voorkomen dat .NET beheerd geheugen wordt gelekt.
DOM-opschoontaken tijdens het verwijderen van onderdelen
Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.
JavaScript-interop-aanroepen zonder circuit
Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.
Aanvullende informatiebronnen
- .NET-methoden aanroepen vanuit JavaScript-functies in ASP.NET Core Blazor
-
InteropComponent.razor
voorbeeld (dotnet/AspNetCore
GitHub-opslagplaatsmain
branch): demain
vertakking vertegenwoordigt de huidige ontwikkeling van de producteenheid voor de volgende release van ASP.NET Core. Als u de vertakking wilt selecteren voor een andere release (bijvoorbeeldrelease/{VERSION}
, waarbij de tijdelijke aanduiding{VERSION}
de releaseversie is), gebruikt u de Switch-vertakkingen of -tags vervolgkeuzelijst om de vertakking te selecteren. Voor een tak die niet meer bestaat, gebruikt u de tab Tags om de API te vinden (bijvoorbeeldv7.0.0
). -
Blazor GitHub-opslagplaats voor voorbeelden (
dotnet/blazor-samples
) (hoe te downloaden) - fouten afhandelen in ASP.NET Core Blazor-apps (sectie vanJavaScript interop)
- Bedreigingsbeperking: JavaScript-functies die worden aangeroepen vanuit .NET-