注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何從 .NET 叫用 JavaScript (JS) 函式。
如需有關如何從 JS 呼叫 .NET 方法的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法。
叫用 JS 函式
IJSRuntime 會由 Blazor 架構進行註冊。 若要從 .NET 呼叫 JS,請插入 IJSRuntime 抽象概念,並呼叫下列其中一種方法:
針對上述會叫用 JS 函式的 .NET 方法:
- 函式識別碼 (
String
) 是相對於全域 (window
) 的。 若要呼叫window.someScope.someFunction
,識別碼為someScope.someFunction
。 在呼叫函式之前,不需要定義函式。 - 將
Object[]
中任意數目的 JSON 可序列化引數傳遞至 JS 函數。 - 取消權杖 (
CancellationToken
) 會傳遞通知作業應取消。 -
TimeSpan
代表 JS 作業的時間限制。 -
TValue
傳回型別也必須有 JSON 可序列化特性。TValue
應符合與所傳回 JSON 型別對應程度最佳的 .NET 型別。 -
InvokeAsync
方法會返回 JSPromise
。InvokeAsync
會將Promise
解除包裝,並傳回Promise
所等候的值。
已啟用預先轉譯 (此為伺服器端應用程式的預設值) 的 Blazor 應用程式無法在預先轉譯期間呼叫 JS。 如需詳細資訊,請參閱預先轉譯一節。
下列範例是以 TextDecoder
型解碼器 JS 作為基礎。 此範例示範如何從 C# 方法叫用 JS 函式,該方法將需求從開發人員的程式碼卸載至現有的 JS API。
JS 函式會接受來自 C# 方法的位元組陣列、將陣列解碼,再將文字傳回元件來顯示。
<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
return decodedArray;
};
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
下列元件:
- 當選擇按鈕 (
convertArray
) 時,使用 JSInvokeAsync 函式來叫用Convert Array
。 - 在呼叫 JS 函式後,所傳遞的陣列會轉換成字串。 字串會傳回元件來顯示 (
text
)。
CallJs1.razor
:
@page "/call-js-1"
@inject IJSRuntime JS
<PageTitle>Call JS 1</PageTitle>
<h1>Call JS Example 1</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray() =>
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
CallJs1.razor
:
@page "/call-js-1"
@inject IJSRuntime JS
<PageTitle>Call JS 1</PageTitle>
<h1>Call JS Example 1</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray() =>
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new MarkupString(await JS.InvokeAsync<string>("convertArray",
quoteArray));
}
}
受限於使用者手勢的 JavaScript API
本節適用於伺服器端元件。
某些瀏覽器 JavaScript(JS)API 只能在使用者手勢的情境中執行,例如使用 Fullscreen API
。 在伺服器端元件中無法透過 JS Interop 機制呼叫這些 API,因為 UI 事件處理是以非同步方式進行,通常這樣就不再屬於使用者手勢的上下文中。 應用程式必須完全在 JavaScript 中處理 UI 事件,因此請使用 onclick
而非 Blazor 的 @onclick
指示詞屬性。
叫用 JavaScript 函式而不讀取傳回的值 (InvokeVoidAsync
)
InvokeVoidAsync 的使用時機:
- 不需要 .NET 就能讀取 JavaScript (JS) 呼叫的結果。
- JS 函式傳回 void(0)/void 0 或未定義。
請提供 displayTickerAlert1
JS 函式。 系統會使用 InvokeVoidAsync 來呼叫此函式,而且此函式不會傳回值:
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
元件 (.razor
) 範例 (InvokeVoidAsync
)
TickerChanged
會在下列元件中呼叫 handleTickerChanged1
方法。
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
類別 (.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
會在下列元件中呼叫 handleTickerChanged1
方法。
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses1(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
叫用 JavaScript 函式並讀取傳回的值 (InvokeAsync
)
當 .NET 應讀取 JavaScript (InvokeAsync) 呼叫的結果時,請使用 JS。
請提供 displayTickerAlert2
JS 函式。 下列範例會傳回字串以供呼叫端顯示:
<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
元件 (.razor
) 範例 (InvokeAsync
)
TickerChanged
會呼叫 handleTickerChanged2
方法,並在下列元件中顯示傳回的字串。
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
類別 (.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
會呼叫 handleTickerChanged2
方法,並在下列元件中顯示傳回的字串。
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses2(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
動態內容產生案例
若要使用 BuildRenderTree 產生動態內容,請使用 [Inject]
屬性:
[Inject]
IJSRuntime JS { get; set; }
預渲染
本節適用於會預先轉譯 Razor 元件的伺服器端應用程式。 Prerender ASP.NET Core Razor 元件中介紹了預先轉譯。
注意
在中的Blazor Web App的內部導航不涉及向伺服器請求新頁面內容。 因此,內部頁面的請求不會進行預渲染。 如果應用程式採用互動式路由,請針對示範預先轉譯行為的元件範例執行完整頁面重載。 如需詳細資訊,請參閱預先轉譯 ASP.NET Core Razor 元件。
本節適用於會預先轉譯 Blazor WebAssembly 元件的伺服器端應用程式和所裝載的 Razor 應用程式。 預先呈現涵蓋在 整合 ASP.NET Core Razor 元件與 MVC 或 Razor 頁面 中。
在預先轉譯期間,無法呼叫 JavaScript(JS)。 下列範例示範如何透過與預先轉譯相容的方式,將 JS Interop 作為元件初始化邏輯的一部分。
下列 scrollElementIntoView
函式:
- 使用
scrollIntoView
捲動至目標元素。 - 從
top
方法傳回元素的getBoundingClientRect
屬性值。
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
當 IJSRuntime.InvokeAsync 在元件程式碼呼叫 JS 函式時, ElementReference 僅在 OnAfterRenderAsync 使用,而不在任何先前的生命週期方法中使用,因為要等到元件轉譯後才會有 HTML DOM 元素。
呼叫 StateHasChanged
(參考來源) 以使用從 JS Interop 呼叫所取得的新狀態,將元件的重新轉譯加入佇列 (如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯)。 此程式碼不會無限迴圈,因為只有在 StateHasChanged 是 scrollPosition
時才會呼叫 null
。
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
上述範例會使用全域函式敗壞用戶端。 如需更適合生產應用程式的方法,請參閱 JavaScript 模組中的 JavaScript 隔離。
用戶端元件中的同步互操作
本節僅適用於用戶端元件。
不論所呼叫的程式碼是同步還是非同步的,JS Interop 通話都是非同步的。 為了確保元件能在伺服器端和客戶端呈現模式中相容,呼叫會以非同步方式進行。 在伺服器上,所有 JS Interop 呼叫都必須以非同步方式進行,因為這些呼叫會透過網路連線來傳送。
如果您確定元件只會在 WebAssembly 上執行,則可以選擇進行同步的 JS Interop 呼叫。 進行同步呼叫的額外負荷會略低於進行非同步呼叫,而且可減少轉譯週期,因為在等候結果時不會有中繼狀態。
若要在用戶端元件中進行從 .NET 到 JavaScript 的同步呼叫,請將 IJSRuntime 轉換成 IJSInProcessRuntime 以進行 JS Interop 呼叫:
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
在 ASP.NET Core 5.0 或更新版本的用戶端元件中使用 IJSObjectReference 時,您可以改為以同步方式使用 IJSInProcessObjectReference。 IJSInProcessObjectReference 會實作 IAsyncDisposable/IDisposable,並應釋放以供垃圾回收,以防止記憶體洩漏,如下列範例所示:
@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();
}
}
}
在上述範例中,JSDisconnectedException 不會在模組處置過程中被截獲,因為在 Blazor - SignalR 線路中,Blazor WebAssembly 應用程式中不存在可遺失的電路。 如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
JavaScript 位置
請使用 JS所述的任何方法載入 JavaScript () 程式碼:
-
在
<head>
標記 中載入指令碼 (通常不建議使用) -
在
<body>
標記中載入指令碼 -
從外部 JavaScript 檔案載入指令碼 (
.js
) - 在 Blazor 啟動後插入指令碼
如需有關在 JS 模組中隔離指令碼的資訊,請參閱 JavaScript 模組中的 JavaScript 隔離一節。
警告
請勿將 <script>
標籤放在元件檔案 (.razor
) 中,因為 <script>
標籤無法動態更新。
JavaScript 模組中的 JavaScript 隔離
Blazor 啟用標準 JS (ECMAScript 規範) 中的 JavaScript () 隔離。 Blazor 中的 JavaScript 模組載入運作方式與其他型別的 Web 應用程式相同,因此您可以任意地自訂在應用程式中定義模組的方式。 如需有關如何使用 JavaScript 模組的指南,請參閱 MDN Web 文件:JavaScript 模組。
JS 隔離提供下列優點:
- 已匯入的 JS 不再會產生全域命名空間問題。
- 程式庫和元件的使用者不需要匯入相關JS。
使用import()
運算子的動態載入已支援於ASP.NET Core和。
if ({CONDITION}) import("/additionalModule.js");
在上述範例中,{CONDITION}
預留位置代表用來判斷是否應該載入模組的條件式檢查。
關於瀏覽器相容性,請參閱我是否可以使用:JavaScript 模組:動態匯入。
在伺服器端情境中,當JS的Blazor電路斷開後,無法進行SignalR的互操作呼叫。 在元件處置期間沒有線路,或在線路不存在的任何其他時間,下列方法呼叫會失敗,並以 JSDisconnectedException 記錄線路中斷連線的訊息:
- JS Interop 方法呼叫
-
Dispose
/DisposeAsync
呼叫任何 IJSObjectReference。
為了避免在伺服器端記錄JSDisconnectedException或記錄自定義資訊Blazor,請在try-catch
語句中攔截例外狀況。
針對以下元件的處置示例:
- 伺服器端元件會實作 IAsyncDisposable。
-
module
IJSObjectReference是一個JS模組。 - JSDisconnectedException 已攔截且未被紀錄。
- 您可以選擇性地在
catch
陳述式中記錄自訂資訊,不論您偏好的記錄層級為何。 下列範例不會記錄自定義資訊。 程式代碼假設開發人員不在意元件處置期間線路何時或何處中斷連線。
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (module is not null)
{
await module.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
如果您在伺服器端 Blazor 應用程式中遺失線路之後,必須清除自己的 JS 物件,或在用戶端上執行其他 JS 程式代碼,請在用戶端的 JS 中使用 MutationObserver
模式。 模式 MutationObserver
可讓您在從 DOM 移除項目時執行 JS 程式碼。
如需詳細資訊,請參閱下列文章:
-
ASP.NET Core Blazor JavaScript 互操作性(JS interop):包含一個展示該模式的代碼範例
MutationObserver
。 - 處理 ASP.NET Core Blazor 應用程式中的錯誤:JavaScript Interop 一節討論 JS Interop 案例中的錯誤處理。
- ASP.NET Core Razor 元件處置:本文說明如何在 Razor 元件中實作處置模式。
下列 JS 模組會匯出顯示 JS 瀏覽器視窗提示的 函式。 請將下列 JS 程式碼放在外部 JS 檔案中。
wwwroot/scripts.js
:
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}
將上述 JS 模組新增至應用程式或類別庫作為 wwwroot
資料夾中的靜態 Web 資產,然後在 InvokeAsync 執行個體上呼叫 IJSRuntime 以將該模組匯入 .NET 程式碼。
IJSRuntime 模組會被匯入為 IJSObjectReference,它代表 .NET 程式碼中的 JS 物件參考。 使用 IJSObjectReference 即可從模組叫用匯出的 JS 函式。
CallJs6.razor
:
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt() => result = await Prompt("Provide text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJs6.razor
:
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt() => result = await Prompt("Provide text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference module;
private string result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string> Prompt(string message)
{
return await module.InvokeAsync<string>("showPrompt", message);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
在前述範例中:
- 依照慣例,
import
識別碼是特別用於匯入 JS 模組的特殊識別碼。 - 請使用模組的穩定靜態 Web 資產路徑來指定其外部 JS 檔案:
./{SCRIPT PATH AND FILE NAME (.js)}
,其中:- 需要目前目錄 (
./
) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。 -
{SCRIPT PATH AND FILE NAME (.js)}
預留位置是wwwroot
下的路徑和檔案名稱。
- 需要目前目錄 (
- 將 IJSObjectReference 中的 垃圾回收 處置於 IAsyncDisposable.DisposeAsync。
- 請勿在
<script>
之後放置 Blazor 標記,因為在叫用 時,模組會自動被載入並快取。
要有網路要求才能動態匯入模組,因此您只能藉由呼叫 InvokeAsync 以非同步方式實現此作業。
IJSInProcessObjectReference
代表在用戶端元件中可同步調用其函式的 JS 物件的參考。 如需詳細資訊,請參閱用戶端元件中的同步 JS Interop 一節。
注意
當外部 JS 檔案由 Razor 類別庫提供時,請使用模組的穩定靜態 Web 資產路徑來指定該模組的 檔案:JS:
- 需要目前目錄 (
./
) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。 -
{PACKAGE ID}
預留位置是程式庫的封裝識別碼。 如果未在專案檔中指定<PackageId>
,則封裝識別碼預設為專案的組件名稱。 在下列範例中,程式庫的組件名稱是ComponentLibrary
,而且程式庫的專案檔未指定<PackageId>
。 -
{SCRIPT PATH AND FILE NAME (.js)}
預留位置是wwwroot
下的路徑和檔案名稱。 在下列範例中,外部 JS 檔案 (script.js
) 會放在類別庫的wwwroot
資料夾中。 -
module
是元件類別 (IJSObjectReference) 的私有可為空private IJSObjectReference? module;
。
module = await js.InvokeAsync<IJSObjectReference>(
"import", "./_content/ComponentLibrary/scripts.js");
如需詳細資訊,請參閱從類別庫 (RCL) Razor 取用 ASP.NET Core Razor 元件。
整個 Blazor 文件的範例會針對模組檔案使用 .js
副檔名,而不是使用較新的 .mjs
副檔名 (RFC 9239)。 我們的文件會繼續使用 .js
副檔名,原因和 Mozilla Foundation 的文件會繼續使用 .js
副檔名的原因相同。 如需詳細資訊,請參閱 Aside - .mjs 與 .js (MDN 檔案)。
捕捉對元素的參考
某些 JavaScript (JS) 互操作場景需要 HTML 元素的參考。 例如,UI 程式庫可能需要元素參考以進行初始化,或者您可能需要在元素上呼叫類似命令的 API,例如 click
或 play
。
請使用下列方法擷取元件中 HTML 元素的參考:
- 將
@ref
屬性新增至 HTML 元素。 - 定義其名稱符合 ElementReference 屬性值的
@ref
型別欄位。
下列範例顯示如何擷取 username
<input>
元素的參考:
<input @ref="username" ... />
@code {
private ElementReference username;
}
警告
請只使用元素參考來變動不會與 Blazor 互動的空白元素內容。 在第三方 API 提供內容給該元素時,此案例很有用。 因為 Blazor 不會與該元素互動,因此該元素的 Blazor 表示法與 DOM 之間不可能發生衝突。
在下列範例中,透過 interop 使用 ul
來變動未排序清單 (MyList
) 的內容是危險的,因為 JS 會與 DOM 互動,從 Blazor 物件填入這個元素的清單項目 (<li>
):
<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>
僅使用 MyList
元素參考來讀取 DOM 內容或觸發事件是受到支援的做法。
如果JS Interop 變更了元素的內容,而MyList
嘗試將差異應用到該元素,那麼這些差異將不會與 DOM 相符。 使用 JS 元素參考透過 MyList
Interop 修改清單的內容是不支援的。
如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
ElementReference 會透過 JS Interop 傳遞至 JS 程式碼。
JS 程式碼會收到一個 HTMLElement
執行個體,該執行個體可以與一般 DOM API 一起使用。 例如,下列程式碼會定義能夠將滑鼠點擊傳送至元素的 .NET 擴充方法 (TriggerClickEvent
)。
JS 函式 clickElement
會在所傳遞的 HTML 元素 (click
) 上建立 element
事件:
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}
若要呼叫未傳回值的 JS 函式,請使用 JSRuntimeExtensions.InvokeVoidAsync。 下列程式碼藉由將擷取到的click
傳遞給先前的JS函式,來觸發用戶端ElementReference事件:
@inject IJSRuntime JS
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await JS.InvokeVoidAsync(
"interopFunctions.clickElement", exampleButton);
}
}
若要使用擴充方法,請建立一個能夠接收 IJSRuntime 執行個體的靜態擴充方法:
public static async Task TriggerClickEvent(this ElementReference elementRef,
IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}
系統會直接在該物件上呼叫 clickElement
方法。 下列範例假設 TriggerClickEvent
方法可從 JsInteropClasses
命名空間取得:
@inject IJSRuntime JS
@using JsInteropClasses
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await exampleButton.TriggerClickEvent(JS);
}
}
重要
只有在轉譯元件後才會填入 exampleButton
變數。 如果未填入的 ElementReference 傳遞至 JS 程式碼,JS 程式碼會收到 null
值。 若要在元件轉譯完成後操作元件參考,請使用 OnAfterRenderAsync
或 OnAfterRender
元件生命週期方法。
使用泛型型別並傳回值時,請使用 ValueTask<TResult>:
public static ValueTask<T> GenericMethod<T>(
this ElementReference elementRef, IJSRuntime js) =>
js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
{JAVASCRIPT FUNCTION}
預留位置是 JS 函式識別碼。
系統會直接在具有某個型別的物件上呼叫 GenericMethod
。 下列範例假設 GenericMethod
可從 JsInteropClasses
命名空間取得:
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
在不同元件中參考元素
ElementReference 無法在元件之間傳遞,因為:
- 執行個體只有在元件完成渲染後才有保證存在,也就是在元件的OnAfterRender/OnAfterRenderAsync方法執行期間或之後。
-
ElementReference 是
struct
,其無法以元件參數的形式來傳遞。
若要讓父元件能夠將元素參考提供給其他元件使用,父元件可以:
- 允許子元件註冊回呼。
- 在 OnAfterRender 事件中調用已註冊的回呼,並傳入元素參考。 此方法可間接地讓子元件與父元件的元素參考互動。
<style>
.red { color: red }
</style>
<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
CallJs7.razor
(父元件):
@page "/call-js-7"
<PageTitle>Call JS 7</PageTitle>
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJs7.razor
(父元件):
@page "/call-js-7"
<PageTitle>Call JS 7</PageTitle>
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(父元件):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(父元件):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(父元件):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(父元件):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJs7.razor.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJs7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJs7.razor.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJs7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
在上述範例中,應用程式的命名空間是 BlazorSample
。 如果在本機測試程式碼,請更新命名空間。
SurveyPrompt.razor
(子元件):
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
SurveyPrompt.razor.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [ value, "red" ]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [ value, "red" ]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS?.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS?.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS?.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS?.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
在上述範例中,應用程式的命名空間是 BlazorSample
與 Shared
資料夾中的共用元件。 如果在本機測試程式碼,請更新命名空間。
強化 JavaScript Interop 呼叫
本節僅適用於互動式伺服器元件,但如果條件適合,用戶端元件也可以設定 JS Interop 逾時。
在具有伺服器互動功能的伺服器端應用程式中,JavaScript (JS) Interop 可能會因為網路錯誤而失敗,因此請將其視為不可靠的應用程式。 Blazor 應用程式會針對 JS 互操作呼叫使用一分鐘的超時。 如果應用程式可以容許更短的超時時間,請使用下列其中一種方法來設定超時時間。
使用 Program.cs
在 CircuitOptions.JSInteropDefaultCallTimeout 中設定全域逾時:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});
builder.Services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
在Startup.ConfigureServices
的Startup.cs
方法中,使用CircuitOptions.JSInteropDefaultCallTimeout設定全域逾時:
services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
{TIMEOUT}
預留位置是 TimeSpan(例如,TimeSpan.FromSeconds(80)
)。
在元件程式碼中設定個別叫用逾時。 指定的超時會覆蓋由 JSInteropDefaultCallTimeout 設定的全域超時:
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, [ "Arg1" ]);
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });
在前述範例中:
-
{TIMEOUT}
的預留位置是 TimeSpan,例如TimeSpan.FromSeconds(80)
。 -
{ID}
預留位置是所要叫用函式的識別碼。 例如,值someScope.someFunction
會叫用函式window.someScope.someFunction
。
雖然 JS 互操作性失敗的常見原因是與伺服器端元件相關的網路故障,但您可以為用戶端元件的 JS 呼叫設定每次呼叫的逾時。 雖然客戶端元件沒有 Blazor-SignalR 電路存在,但由於其他適用的原因,JS Interop 呼叫可能會失敗。
如需資源耗盡的詳細資訊,請參閱 ASP.NET Core Blazor 互動式伺服器端轉譯的威脅風險降低指導。
避免循環的物件參考
包含循環參考的物件無法在用戶端序列化:
- .NET 方法調用。
- 當傳回型別具有循環參考時,來自 C# 的 JavaScript 方法呼叫。
會轉譯 UI 的 JavaScript 程式庫
您有時可能會希望使用能在瀏覽器 DOM 內產生可見使用者介面元素的 JavaScript (JS) 程式庫。 乍看之下,這似乎很困難,因為 Blazor 的差異系統仰賴於對 DOM 元素樹狀結構的控制力,如果某些外部程式碼變動 DOM 樹狀結構而使其套用差異的機制失效,就會發生錯誤。 這不是 Blazor 特有的限制。 任何差異型 UI 架構都會發生相同的挑戰。
幸運的是,可以在 Razor 元件 UI 中可靠地簡單內嵌外部產生的 UI。 建議使用的技術是讓元件的程式碼 (.razor
檔案) 產生空白元素。 就 Blazor 的差異系統而言,該元素始終視為空的,因此轉譯器不會深入到元素中,而是會保持其內容不變。 此使得能安全地以任意由外部管理的內容填入至元素中。
下列範例會示範此概念。 在 if
陳述式內,當 firstRender
是 true
時,請在 unmanagedElement
外部使用 Blazor Interop 與 JS 互動。 例如,呼叫外部 JS 程式庫以填入元素。 在此元件被移除之前,Blazor 將不會對元素的內容進行任何操作。 元件移除時,元件的整個 DOM 子樹狀結構也會移除。
<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>
<div @ref="unmanagedElement"></div>
@code {
private ElementReference unmanagedElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
...
}
}
}
請考慮下列使用開放原始碼 Mapbox API 來轉譯互動式地圖的範例。
下列 JS 模組會放入應用程式,或設為可從 Razor 類別庫取得。
wwwroot/mapComponent.js
:
import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';
mapboxgl.accessToken = '{ACCESS TOKEN}';
export function addMapToElement(element) {
return new mapboxgl.Map({
container: element,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
}
export function setMapCenter(map, latitude, longitude) {
map.setCenter([longitude, latitude]);
}
若要產生正確樣式,請將下列樣式表標籤新增至主機 HTML 頁面。
將下列 <link>
元素新增至 <head>
元素標記 (<head>
內容的位置):
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
CallJs8.razor
:
@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 8</PageTitle>
<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
try
{
await mapInstance.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
if (mapModule is not null)
{
try
{
await mapModule.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJs8.razor
:
@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 8</PageTitle>
<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
try
{
await mapInstance.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
if (mapModule is not null)
{
try
{
await mapModule.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
上述範例會產生互動式地圖 UI。 使用者:
- 可以進行拖曳以便捲動或縮放。
- 選取會跳至預先定義位置的按鈕。
在前述範例中:
- 就
<div>
而言,@ref="mapElement"
與 Blazor 會保持空白。mapbox-gl.js
指令碼可以安全地填入元素並修改其內容。 請將這項技術與任何會轉譯 UI 的 JS 程式庫搭配使用。 您可以在 JS 元件內嵌來自第三方 Razor SPA 架構的元件,前提是這些第三方元件不會嘗試連線到頁面的其他部分並進行修改。 不安全讓外部程式碼修改那些JS不被視為空白的Blazor元素。 - 使用此方法時,請牢記有關 Blazor 如何保留或終結 DOM 元素的規則。 元件會安全地處理按鈕點擊事件,並更新現有的地圖執行個體,因為系統會盡可能地保留 DOM 元素。 如果您從
@foreach
迴圈內渲染映射元素清單,請使用@key
確保元件實例會獲得保留。 否則,清單資料的變更可能會導致元件實例以不理想的方式保留先前實例的狀態。 如需詳細資訊,請參閱如何使用@key
指示詞屬性來保留元素、元件和模型物件之間的關聯性。 - 此範例會在 JavaScript 模組內封裝 JS 邏輯和相依性,並使用
import
識別碼動態載入模組。 如需詳細資訊,請參閱 JavaScript 模組中的 JavaScript 隔離。
位元組陣列支援
Blazor 支援避免將位元組陣列編碼或解碼為 Base64 的最佳化 JavaScript 位元組陣列互操作 (JS)。 下列範例使用 JS Interop 將位元組陣列傳遞至 JavaScript。
請提供 receiveByteArray
JS 函式。 系統會使用 InvokeVoidAsync 來呼叫此函式,而且此函式不會傳回值:
<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
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);
}
}
如需有關從 JavaScript 呼叫 .NET 時如何使用位元組陣列的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法。
從 .NET 串流至 JavaScript
Blazor 支援直接從 .NET 將資料串流至 JavaScript (JS)。 串流是使用 DotNetStreamReference 建立的。
DotNetStreamReference 代表 .NET 串流,並使用下列參數:
-
stream
:傳送至 JS 的串流。 -
leaveOpen
:判斷串流是否在傳輸後保持開啟。 如果未提供值,則leaveOpen
會預設為false
。
在 JS 中,使用陣列緩衝區或可讀取的串流來接收資料:
使用
ArrayBuffer
:async function streamToJavaScript(streamRef) { const data = await streamRef.arrayBuffer(); }
使用
ReadableStream
:async function streamToJavaScript(streamRef) { const stream = await streamRef.stream(); }
在 C# 程式碼中:
var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);
在前述範例中:
-
{STREAM}
預留位置代表 Stream 傳送至 JS。 -
JS
是所插入的 IJSRuntime 執行個體。
通常不需要處置 DotNetStreamReference 執行個體。 當 leaveOpen
設為其預設值 false
時,基礎 Stream 會在傳輸至 JS 之後自動處置。
如果 leaveOpen
是 true
,則處置 DotNetStreamReference 不會處置其底層的 Stream。 應用程式的程式碼會決定處置基礎 Stream 的時機。 決定如何處置基礎 Stream 時,請考慮下列事項:
- 在 Stream 被傳輸至 JS 時進行處置會被視為應用程式錯誤,並可能導致未處理的例外狀況發生。
- Stream 傳輸會在 DotNetStreamReference 作爲引數傳遞至 JS Interop 呼叫時立即開始,不論資料流是否實際用於 JS 邏輯。
鑒於這些特性,我們建議只在 Stream 完全消耗完之後(JS 或 arrayBuffer
所傳回的承諾解決後)才丟棄基礎的 stream
。 只有當 DotNetStreamReference 邏輯無條件地會取用它時,JS 才應該被傳遞到 JS。
在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法會說明反向作業:從 JavaScript 串流至 .NET。
ASP.NET Core Blazor 檔案下載會說明如何在 Blazor 中下載檔案。
攔截 JavaScript 例外狀況
若要攔截 JS 例外狀況,請將 JS Interop 包裝在 try
-catch
區塊中,並攔截 JSException。
在下列範例中,nonFunction
JS 函式不存在。 當找不到函式時,會以JSException捕捉Message,指出下列錯誤:
Could not find 'nonFunction' ('nonFunction' was undefined).
CallJs11.razor
:
@page "/call-js-11"
@inject IJSRuntime JS
<PageTitle>Call JS 11</PageTitle>
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJs11.razor
:
@page "/call-js-11"
@inject IJSRuntime JS
<PageTitle>Call JS 11</PageTitle>
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string errorMessage;
private string result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string errorMessage;
private string result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
中止長時間執行的 JavaScript 函式
在元件中使用 JSAbortController 和 CancellationTokenSource,從 C# 程式碼中止長時間執行的 JavaScript 函式。
下列 JSHelpers
類別包含模擬的長時間執行函式 longRunningFn
,此函式會一直計算,直到 AbortController.signal
指出系統已呼叫 AbortController.abort
為止。
sleep
函式僅供示範之用,目的是為了模擬長時間執行的函式執行速度緩慢的情況,因此不會出現在生產程式碼中。 當元件呼叫 stopFn
時,系統便會透過 longRunningFn
上的 while
迴圈條件式檢查向 AbortSignal.aborted
發出中止訊號。
<script>
class Helpers {
static #controller = new AbortController();
static async #sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
static async longRunningFn() {
var i = 0;
while (!this.#controller.signal.aborted) {
i++;
console.log(`longRunningFn: ${i}`);
await this.#sleep(1000);
}
}
static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}
window.Helpers = Helpers;
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
下列元件:
- 會在選取 JS 按鈕時叫用
longRunningFn
函式Start Task
。 系統會使用 CancellationTokenSource 來管理長時間執行函式的執行。 CancellationToken.Register 設置一個 JS 互通呼叫委託,以在 JS 被取消時執行stopFn
功能 CancellationTokenSource.Token。 - 當選取了
Cancel Task
按鈕時,會呼叫 CancellationTokenSource.Token 來取消 Cancel。 -
CancellationTokenSource 會在
Dispose
方法中被處置。
CallJs12.razor
:
@page "/call-js-12"
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
CallJsExample12.razor
:
@page "/call-js-example-12"
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
瀏覽器的開發人員工具主控台會在已選取 JS 按鈕後,以及在選取 Start Task
按鈕後中止函式時,指出長時間執行的 Cancel Task
函式的執行狀況:
longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!
JavaScript [JSImport]
/[JSExport]
Interop
本節適用於用戶端元件。
作為在用戶端元件中根據 JS 介面使用 Blazor 的 JS interop 機制與 JavaScript (IJSRuntime) 互動的替代方案,.NET 7 或更高版本為目標的應用程式可以使用 JS[JSImport]
/[JSExport]
interop API。
如需更多資訊,請參閱 JavaScript JSImport/JSExport 與 ASP.NET Core 的互操作性 Blazor。
已解除封送的 JavaScript Interop
本節適用於用戶端元件。
使用 IJSUnmarshalledRuntime 介面進行無封送 Interop 的做法已被淘汰,應改用 JavaScript [JSImport]
/[JSExport]
Interop。
如需詳細資訊,請參閱 JavaScriptJSImport/JSExport 與 ASP.NET Core 之間的互操作性 Blazor。
已解除封送的 JavaScript Interop
當 .NET 物件針對 JavaScript (Blazor WebAssembly) Interop 進行序列化,且下列任一項成立時,JS 元件可能會遇到效能不佳的情形:
- 快速進行大量 .NET 物件的序列化。 例如,在移動輸入裝置 (例如滾動滑鼠滾輪) 的基礎上進行 JS Interop 呼叫時,可能會導致效能不佳。
- 必須將大型 .NET 物件或許多 .NET 物件序列化,以供 JS Interop 使用。 例如,當 JS Interop 呼叫需要序列化數十個檔案時,可能會導致效能不佳。
IJSUnmarshalledObjectReference 代表一個 JS 物件的參考,可以呼叫其功能,而無需擔心 .NET 資料序列化的額外負擔。
在以下範例中:
- 包含字串和整數的結構體被未序列化地傳遞至 。
- JS 函式會處理資料,並將布林值或字串傳回給呼叫端。
-
JS 字串無法直接轉換成 .NET
string
物件。unmarshalledFunctionReturnString
函式會呼叫BINDING.js_string_to_mono_string
以管理 JS 字串的轉換。
<script>
window.returnObjectReference = () => {
return {
unmarshalledFunctionReturnBoolean: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
year === 1968;
},
unmarshalledFunctionReturnString: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
}
};
}
</script>
注意
如需 JS 位置的一般指導和我們對於生產應用程式的建議,請參閱 ASP.NET Core Blazor 應用程式中的 JavaScript 位置
警告
在未來的 .NET 版本中,js_string_to_mono_string
函式名稱、行為和存在與否可能會有變化。 例如:
- 函式可能會重新命名。
- 函式本身可能會遭到移除,以利架構自動轉換字串。
CallJsExample10.razor
:
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string? callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
如果未在 C# 程式碼中處置 IJSUnmarshalledObjectReference 執行個體,則可以在 JS 中處置。 下列 dispose
函式在從 JS 呼叫時會處置物件參考:
window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
return {
dispose: function () {
DotNet.disposeJSObjectReference(this);
},
...
};
}
陣列型別可使用 JS 從 js_typed_array_to_array
物件轉換成 .NET 物件,但 JS 陣列必須是具型別的陣列。
JS 中的陣列可在 C# 程式碼中讀取為 .NET 物件陣列 (object[]
)。
其他資料型別 (例如字串陣列) 可以進行轉換,但需要建立新的 Mono 陣列物件 (mono_obj_array_new
) 並設定其值 (mono_obj_array_set
)。
警告
JS 架構 (例如 Blazor、js_typed_array_to_array
和 mono_obj_array_new
) 所提供的 mono_obj_array_set
函式,在未來的 .NET 版本中可能會發生名稱變更、行為變更或移除的情況。
JavaScript 互操作物件參考的釋放
JavaScript (JS) Interop 文章中的範例會示範典型的物件處置模式:
如本文所述,從 .NET 呼叫 JS 時,請釋放任何在 .NET 或 IJSObjectReference 創建的 /IJSInProcessObjectReference/
JSObjectReference
JS,以避免 JS 記憶體洩漏。當從 JS 呼叫 .NET 時,如 Blazor 中的 所述,請從 .NET 或 DotNetObjectReference 中處置已建立的 JS,以免造成 .NET 記憶體洩漏。
JS Interop 物件參考會實作為一個以識別碼為索引鍵的對應表,由建立參考的 JS Interop 呼叫端負責實現。 當從 .NET 或 JS 端進行物件處置時,Blazor 會從對應中移除該項目,只要物件沒有其他強式參考存在,就可以對物件進行垃圾回收。
至少,務必處置在 .NET 端建立的物件,以免 .NET 受控記憶體洩漏。
元件處置期間的 DOM 清除工作
如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
沒有迴路的 JavaScript 互操作呼叫
如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
其他資源
- 在 ASP.NET Core 中從 JavaScript 函式呼叫 .NET 方法Blazor
-
InteropComponent.razor
範例 (dotnet/AspNetCore
GitHub 存放庫main
分支):main
分支代表產品單位針對下一版 ASP.NET Core 的目前開發項目。 若要選取不同版本的分支(例如,release/{VERSION}
,其中{VERSION}
是發行版本),請使用 切換分支或標籤 下拉式清單來選取分支。 對於已不存在的分支,請使用標籤標籤來尋找 API(例如,v7.0.0
)。 -
Blazor 範例 GitHub 存放庫 (
dotnet/blazor-samples
) (如何下載) - 處理 ASP.NET Core Blazor 應用程式中的錯誤 (JavaScript Interop 區段)
- 威脅風險降低:從 .NET 叫用的 JavaScript 函式