從 ASP.NET Core Blazor 中的 .NET 方法呼叫 JavaScript 函式

本文說明如何從 .NET 叫用 JavaScript (JS) 函式。

如需如何從 JS 呼叫 .NET 方法的詳細資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

IJSRuntime 是由 Blazor 架構註冊。 若要從 .NET 呼叫 , JS 請插入 IJSRuntime 抽象概念,並呼叫下列其中一種方法:

針對上述叫 JS 用函式的 .NET 方法:

  • 函式識別碼 (String) 相對於全域範圍 (window) 。 若要呼叫 window.someScope.someFunction ,識別碼為 someScope.someFunction 。 在呼叫函式之前,不需要先註冊函式。
  • 將任意數目的 JS ON 序列化引數 Object[] 傳遞至函 JS 式。
  • 取消權杖 (CancellationToken) 傳播應取消作業的通知。
  • TimeSpan 表示作業的時間限制 JS 。
  • TValue 回型別也必須是 JS ON 可序列化。 TValue 應該符合最能對應至傳回 ON JS 類型的 .NET 類型。
  • JS Promise方法會傳 InvokeAsync 回 。 InvokeAsync 解除包裝 Promise ,並傳回 所 Promise 等候的值。

針對 Blazor 已啟用預先呈現的應用程式,在預先呈現期間無法呼叫 JS 。 如需詳細資訊,請參閱 Prerendering 一 節。

下列範例是以 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);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

下列 CallJsExample1 元件:

  • convertArrayJS 選取按鈕 Convert Array 時 () 叫用 InvokeAsync 函式。
  • 呼叫函 JS 式之後,傳遞的陣列會轉換成字串。 字串會傳回給元件,以顯示 (text) 。

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

JavaScript API 受限於使用者手勢

本節僅適用于 Blazor Server 應用程式。

某些瀏覽器 JavaScript (JS) API 只能在使用者手勢的內容中執行,例如使用Fullscreen API (MDN 檔) 。 這些 API 無法透過 JS 應用程式中的 Interop 機制呼叫,因為 UI 事件處理是以非同步方式 Blazor Server 執行,而且通常不再在使用者手勢的內容中執行。 應用程式必須在 JavaScript 中完全處理 UI 事件,因此請使用 onclick 而非 Blazor 的 @onclick 指示詞屬性。

叫用 JavaScript 函式而不讀取傳回的值 () InvokeVoidAsync

使用 InvokeVoidAsync 時機:

提供函式 displayTickerAlert1JS 。 函式會使用 呼叫 InvokeVoidAsync ,且不會傳回值:

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

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

元件 (.razor) 範例 (InvokeVoidAsync)

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample2 元件中的 方法。

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

類別 (.cs) 範例 (InvokeVoidAsync)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample3 元件中的 方法。

Pages/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()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

叫用 JavaScript 函式並讀取傳回的值 (InvokeAsync)

InvokeAsync .NET 應該讀取 JavaScript (呼叫 JS 的結果時,請使用) 。

提供函式 displayTickerAlert2JS 。 下列範例會傳回字串,供呼叫端顯示:

<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 互通性 (JS Interop)

元件 (.razor) 範例 (InvokeAsync)

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample4 元件中顯示傳回的字串。

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

類別 (.cs) 範例 (InvokeAsync)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample5 元件中顯示傳回的字串。

Pages/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()
    {
        if (jsClass is not null)
        {
            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; }

預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript (JS) 等特定動作。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式會使用 呼叫 JSRuntimeExtensions.InvokeVoidAsync ,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

OnAfterRender{Async}伺服器上的預先呈現程式期間,不會呼叫生命週期事件。 OnAfterRender{Async}覆寫 方法,以延遲 JS Interop 呼叫,直到在預先呈現元件後於用戶端上呈現和互動式為止。

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

下列元件示範如何使用 JS Interop 做為元件初始化邏輯的一部分,以與預先呈現相容的方式使用。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 的位置 JSRuntime.InvokeAsync 時, ElementReference 只會在任何先前的生命週期方法中使用 OnAfterRenderAsync ,因為在轉譯元件之後,沒有 JS 元素。

StateHasChanged會呼叫 以使用從 JS Interop 呼叫 (取得的新狀態來重新呈現元件,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 是 nulldata 才會呼叫。

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

應用程式中的 Blazor WebAssembly 同步 JS Interop

本節僅適用于 Blazor WebAssembly 應用程式。

不論所呼叫的程式碼是同步還是非同步,JS Interop 呼叫預設都是非同步。 根據預設,呼叫為非同步,以確保元件在 Blazor 裝載模型 (Blazor Server 和 Blazor WebAssembly) 之間都相容。 在 上 Blazor Server ,所有 JS Interop 呼叫都必須是非同步,因為它們是透過網路連線傳送的。

如果您知道您的應用程式只會在 上 Blazor WebAssembly 執行,您可以選擇進行同步 JS Interop 呼叫。 這比進行非同步呼叫稍微少一些額外負荷,而且可能會導致轉譯週期較少,因為等候結果時沒有中繼狀態。

若要在 Blazor WebAssembly 應用程式中從 .NET 同步呼叫 JavaScript,請轉換成 IJSRuntimeIJSInProcessRuntime 以進行 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 或更新版本中 Blazor WebAssembly 使用 IJSObjectReference 時,您可以改為以同步方式使用 IJSInProcessObjectReference

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

JavaScript 的位置

使用JavaScript () 互通性 (互通性) 概觀一文所述,載入 JavaScript (JS) 程式碼: JS

如需在模組中JS隔離腳本的詳細資訊,請參閱JavaScript 模組中的 JavaScript 隔離一節。

警告

請勿將 <script> 標記放在元件檔案中 (.razor) , <script> 因為無法動態更新標記。

JavaScript 模組中的 JavaScript 隔離

Blazor 會啟用標準 JavaScript 模組 (ECMAScript 規格) 中的 JavaScript (JS) 隔離。

JS 隔離提供下列優點:

  • 已匯入的 JS 不再會產生全域命名空間問題。
  • 不需要程式庫和元件的取用者即可匯入相關 JS。

例如,下列 JS 模組會匯出 JS 用於顯示 瀏覽器視窗提示的函式。 將下列 JS 程式碼放在外部 JS 檔案中。

wwwroot/scripts.js:

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

將上述 JS 模組新增至應用程式或類別庫作為資料夾中的靜態 Web 資產 wwwroot ,然後在 實例上 IJSRuntime 呼叫 InvokeAsync ,將模組匯入 .NET 程式碼。

IJSRuntime 會將模組匯入為 IJSObjectReference ,代表來自 .NET 程式碼之 物件的參考 JS 。 IJSObjectReference使用 從模組叫用匯出的 JS 函式。

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

在上述範例中:

  • 根據慣例, import 識別碼是特別用來匯入 JS 模組的特殊識別碼。
  • 使用模組的穩定靜態 Web 資產路徑指定模組的外部 JS 檔案: ./{SCRIPT PATH AND FILENAME (.js)} ,其中:
    • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
    • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。
  • IJSObjectReference處置 中的 IAsyncDisposable.DisposeAsync垃圾收集

動態匯入模組需要網路要求,因此只能藉由呼叫 InvokeAsync 以非同步方式達成。

IJSInProcessObjectReference 表示物件的參考, JS 其函式可以在應用程式中同步 Blazor WebAssembly 叫用。 如需詳細資訊,請參閱應用程式中的 Blazor WebAssembly 同步 JS Interop一節。

注意

類別庫提供 Razor外部 JS 檔案時,請使用其穩定的靜態 Web 資產路徑來指定模組的 JS 檔案: : ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
  • {PACKAGE ID} 預留位置是程式庫的封裝識別碼。 如果未在專案檔中指定 <PackageId>,則封裝識別碼預設為專案的組件名稱。 在下列範例中,程式庫的元件名稱是 ComponentLibrary ,而且程式庫的專案檔未指定 <PackageId>
  • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。 在下列範例中 JS ,外部檔案 () script.js 放在類別庫的 wwwroot 資料夾中。
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

如需詳細資訊,請參閱從 Razor 類別庫 (RCL) 取用 ASP.NET Core Razor

擷取元素的參考

某些 JavaScript () JS Interop 案例需要 HTML 元素的參考。 例如,UI 程式庫可能需要元素參考進行初始化,或者您可能需要在 或 等 clickplay 專案上呼叫類似命令的 API。

使用下列方法擷取元件中 HTML 元素的參考:

  • @ref將屬性新增至 HTML 專案。
  • 定義類型的 ElementReference 欄位,其名稱符合屬性的值 @ref

下列範例示範擷取元素的 username<input> 參考:

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

@code {
    private ElementReference username;
}

警告

只使用元素參考來變動未與 Blazor 互動之空白元素的內容。 當協力廠商 API 提供內容給 元素時,此案例很有用。 因為 Blazor 不會與元素互動,所以在 元素的表示與 Document Object Model (DOM) 之間 Blazor 沒有衝突的可能性。

在下列範例中,將未排序清單的內容 (ul) 變動會很危險,因為 Blazor 與 DOM 互動,以從 Todos 物件 () 填入這個專案的清單專案 <li>

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

如果 JS Interop 會變動元素 MyList 的內容,並 Blazor 嘗試將差異套用至元素,則差異不會與 DOM 相符。

如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

ElementReference 透過 JS Interop 傳遞至 JS 程式碼。 程式 JS 代碼會 HTMLElement 接收 實例,其可與一般 DOM API 搭配使用。 例如,下列程式碼會定義 .NET 擴充方法 (TriggerClickEvent) ,以便將滑鼠點選傳送至專案。

函 JS 式 clickElement 會在 click 傳遞的 HTML 專案上建立事件, (element) :

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

若要呼叫 JS 未傳回值的函式,請使用 JSRuntimeExtensions.InvokeVoidAsync 。 下列程式碼會呼叫 JS 上述函式並擷取 ElementReference ,以觸發用戶端 click 事件:

@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 。 若要在元件完成轉譯之後操作元素參考,請使用OnAfterRenderAsyncOnAfterRender 元件生命週期方法

使用泛型型別並傳回值時,請使用 ValueTask<TResult>

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

{JAVASCRIPT FUNCTION}預留位置是函 JS 式識別碼。

GenericMethod 直接在具有 型別的 物件上呼叫 。 下列範例假設 GenericMethod 可從 JsInteropClasses 命名空間取得 :

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

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

跨元件參考元素

ElementReference無法在元件之間傳遞,因為:

若要讓父元件讓元素參考可供其他元件使用,父元件可以:

  • 允許子元件註冊回呼。
  • 使用傳遞的專案參考在 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 互通性 (JS Interop)

Pages/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?" />

Pages/CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的元件 Pages 。 如果在本機測試程式碼,請更新 命名空間。

Shared/SurveyPrompt.razor (子元件) :

@inject IJSRuntime JS

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

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

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

        [Parameter]
        public IObservable<ElementReference>? Parent { 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();
        }
    }
}

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的共用元件 Shared 。 如果在本機測試程式碼,請更新 命名空間。

強化 JavaScript Interop 呼叫

本節主要適用于 Blazor Server 應用程式,但 Blazor WebAssembly 如果條件保證,應用程式也可能設定 JS Interop 逾時。

在 Blazor Server 應用程式中,JavaScript (JS) Interop 可能會因為網路錯誤而失敗,而且應該視為不可靠。 根據預設, Blazor Server 應用程式會針對 JS Interop 呼叫使用一分鐘逾時。 如果應用程式可以容許更積極逾時,請使用下列其中一種方法來設定逾時。

使用 CircuitOptions.JSInteropDefaultCallTimeout 設定 中的 Program.cs 全域逾時:

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

{TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)

在元件程式碼中設定每個調用逾時。 指定的逾時會覆寫 所 JSInteropDefaultCallTimeout 設定的全域逾時:

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

在上述範例中:

  • {TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)
  • {ID}預留位置是要叫用之函式的識別碼。 例如,值 someScope.someFunction 會叫用 函式 window.someScope.someFunction

雖然 Interop 失敗的常見原因是 JS 應用程式中的網路失敗 Blazor Server ,但可以針對應用程式中的 Interop 呼叫 Blazor WebAssembly 設定每個叫用 JS 逾時。 雖然應用程式中沒有任何 SignalR 線路存在, JS 但 Interop 呼叫可能會因為應用程式中套用 Blazor WebAssembly 的其他 Blazor WebAssembly 原因而失敗。

如需資源耗盡的詳細資訊,請參閱ASP.NET Core Blazor Server 的威脅防護指引

避免迴圈物件參考

包含迴圈參考的物件無法在用戶端上序列化下列其中一項:

  • .NET 方法呼叫。
  • 當傳回型別具有迴圈參考時,JavaScript 方法會從 C# 呼叫。

轉譯 UI 的 JavaScript 程式庫

有時候,您可能會想要使用 JavaScript (JS) 程式庫,在瀏覽器 Document Object Model (DOM) 內產生可見的使用者介面元素。 第一眼,這看起來可能很困難,因為 Blazor 差異系統依賴控制 DOM 元素的樹狀結構,並在某些外部程式碼變動 DOM 樹狀結構並使其套用差異的機制失效時發生錯誤。 這不是特定的 Blazor 限制。 任何差異型 UI 架構都會發生相同的挑戰。

幸運的是,在元件 UI 中可靠地內 Razor 嵌外部產生的 UI 相當簡單。 建議的技術是讓元件的程式碼 (.razor 檔案產生空白元素) 。 Blazor就差異系統而言,元素一律是空的,因此轉譯器不會遞迴到元素中,而是將其內容單獨保留。 這可讓您安全地以任意外部管理的內容填入元素。

下列範例示範概念。 當 為 truefirstRender ,在 if 語句中,使用 unmanagedElement Interop 與 外部 BlazorJS 互動。 例如,呼叫外部連結 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 類別庫取得。

注意

若要建立 Mapbox 對應,請從 Mapbox 登入 取得存取權杖,並提供它出現在下列程式碼中的位置 {ACCESS TOKEN}

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

若要產生正確的樣式,請將下列樣式表單標籤新增至主機 HTML 頁面。

將下列 <link> 專案新增至 <head>內容) (位置的專案 <head> 標記:

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

Pages/CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

上述範例會產生互動式地圖 UI。 使用者:

  • 可以拖曳以捲動或縮放。
  • 選取按鈕以跳至預先定義的位置。

日本的 Mapbox 街道地圖,其中具有選取 B,英國和東京、日本的按鈕

在上述範例中:

  • <div>@ref="mapElement" 保持空白,就 Blazor 如同所考慮。 腳本 mapbox-gl.js 可以安全地填入元素,並修改其內容。 將這項技術與轉譯 UI 的任何 JS 程式庫搭配使用。 只要元件未嘗試連絡並修改頁面的其他部分,您就可以從協力廠商 JS SPA 架構內 Razor 嵌元件。 外部程式 JS 代碼修改不視為空白的專案 Blazor 並不安全。
  • 使用此方法時,請記住如何 Blazor 保留或終結 DOM 元素的規則。 元件會安全地處理按鈕點選事件,並更新現有的對應實例,因為預設會保留 DOM 元素。 如果您要從迴圈內 @foreach 轉譯對應專案清單,則想要使用 @key 來確保元件實例的保留。 否則,清單資料的變更可能會導致元件實例以不想要的方式保留先前實例的狀態。 如需詳細資訊,請參閱using @key to preserve elements and components
  • 此範例會在 JS ES6 模組中封裝邏輯和相依性,並使用 import 識別碼動態載入模組。 如需詳細資訊,請參閱 JavaScript 模組中的 JavaScript 隔離

位元組陣列支援

Blazor 支援優化位元組陣列 JavaScript (JS) Interop,以避免將位元組陣列編碼/解碼為 Base64。 下列範例會使用 JS Interop 將位元組陣列傳遞至 JavaScript。

提供函 receiveByteArrayJS 式。 函式會使用 InvokeVoidAsync 呼叫,而且不會傳回值:

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

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

Pages/CallJsExample9.razor:

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

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

如需從 JavaScript 呼叫 .NET 時使用位元組陣列的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

JavaScript Interop 呼叫的大小限制

本節僅適用于 Blazor Server 應用程式。 在 中 Blazor WebAssembly ,架構不會限制 JavaScript () JS Interop 輸入和輸出的大小。

在 中 Blazor Server , JS Interop 呼叫的大小受限於中樞方法允許的傳入 SignalR 訊息大小上限,預設會 HubOptions.MaximumReceiveMessageSize 強制執行 (:32 KB) 。 JS 大於擲回錯誤的 .NET SignalR 訊息 MaximumReceiveMessageSize 。 架構不會限制從中樞到用戶端的 SignalR 訊息大小。

當記錄未設定為 [錯] 或 [追蹤]時 SignalR ,訊息大小錯誤只會出現在瀏覽器的開發人員工具主控台中:

錯誤:連線中斷連線時發生錯誤「錯誤:伺服器在關閉時傳回錯誤:連線已關閉併發生錯誤。」。

當伺服器端記錄設定為 [偵錯] 或 [追蹤]SignalR,伺服器端記錄會顯示 InvalidDataException 訊息大小錯誤的 。

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

錯誤:

System.IO.InvalidDataException:超過訊息大小上限 32768B。 您可以在 AddHubOptions 中設定訊息大小。

在 中 Program.cs 設定 MaximumReceiveMessageSize ,以增加限制。 下列範例會將接收訊息大小上限設定為 64 KB (64 * 1024) :

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR增加傳入訊息大小限制是需要更多伺服器資源的成本,而且會公開伺服器,以增加惡意使用者的風險。 此外,將大量內容讀入記憶體做為字串或位元組陣列,也會導致使用垃圾收集行程不佳的配置,因而造成額外的效能負面影響。

開發在應用程式中 BlazorBlazor Server 傳輸大量資料 JS 的程式碼時,請考慮下列指引:

  • 利用原生串流 Interop 支援來傳輸大於 SignalR 傳入訊息大小限制的資料:
  • 一般秘訣:
    • 請勿在 和 C# 程式碼中 JS 配置大型物件。
    • 當進程完成或取消時,釋放已耗用的記憶體。
    • 基於安全性目的,強制執行下列額外需求:
      • 宣告可以傳遞的檔案或資料大小上限。
      • 宣告從用戶端到伺服器的最小上傳速率。
    • 伺服器收到資料之後,資料可以是:
      • 暫時儲存在記憶體緩衝區中,直到收集所有區段為止。
      • 立即取用。 例如,資料可以立即儲存在資料庫中,或在收到每個區段時寫入磁片。

Unmarshalled JavaScript Interop

Blazor WebAssembly 當 .NET 物件序列化為 JavaScript () JS Interop 且下列任一項成立時,元件可能會遇到效能不佳的情況:

  • 大量 .NET 物件會快速序列化。 例如,當 Interop 呼叫是以移動輸入裝置為基礎進行時 JS ,效能不佳,例如旋轉滑鼠滾輪。
  • 大型 .NET 物件或許多 .NET 物件必須針對 Interop 序列化 JS 。 例如,當 Interop 呼叫需要序列化數十個檔案時 JS ,效能不佳可能會導致。

IJSUnmarshalledObjectReference 表示可以叫用其函式的物件參考 JS ,而不需要序列化 .NET 資料的額外負荷。

在下例中︰

  • 包含字串和整數 的結構 會傳遞至 未序列化為 JS 。
  • JS 函式會處理資料,並將布林值或字串傳回給呼叫端。
  • JS字串無法直接轉換成 .NET string 物件。 函 unmarshalledFunctionReturnString 式會呼叫 BINDING.js_string_to_mono_string 來管理字串的 JS 轉換。

注意

下列範例不是此案例的典型使用 案例,因為 傳遞至 JS 的結構不會造成元件效能不佳。 此範例只會使用小型物件來示範傳遞未序列化 .NET 資料的概念。

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

警告

在未來的 .NET 版本中,函 js_string_to_mono_string 式名稱、行為和存在可能會變更。 例如:

  • 函式可能會重新命名。
  • 函式本身可能會遭到移除,以利架構自動轉換字串。

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

IJSUnmarshalledObjectReference如果實例未在 C# 程式碼中處置,則可以在 中 JS 處置它。 下列 dispose 函式會在從 JS 呼叫 時處置物件參考:

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

    ...
  };
}

陣列類型可以使用 從 JS 物件轉換成 .NET 物件 js_typed_array_to_array ,但 JS 陣列必須是具型別陣列。 從 JS 的陣列可以在 C# 程式碼中讀取為 .NET 物件陣列, (object[]) 。

您可以轉換其他資料類型,例如字串陣列,但需要建立新的 Mono 陣列物件 (mono_obj_array_new) 並設定其值 (mono_obj_array_set) 。

警告

JS 架構所提供的 Blazor 函式,例如 js_typed_array_to_arraymono_obj_array_newmono_obj_array_set ,受限於名稱變更、行為變更,或在未來的 .NET 版本中移除。

從 .NET 串流至 JavaScript

Blazor 支援直接從 .NET 串流至 JavaScript 的資料。 資料流程是使用 建立的 DotNetStreamReference

DotNetStreamReference 表示 .NET 資料流程,並使用下列參數:

  • stream:傳送至 JavaScript 的資料流程。
  • leaveOpen:判斷資料流程是否在傳輸之後保持開啟。 如果未提供值, leaveOpen 則預設為 false

在 JavaScript 中,使用陣列緩衝區或可讀取的資料流程來接收資料:

  • ArrayBuffer使用 :

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • ReadableStream使用 :

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

在 C# 程式碼中:

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

在上述範例中:

  • {STREAM} 位符代表 Stream 傳送至 JavaScript 的 。
  • JS 是插入的 IJSRuntime 實例。

從 ASP.NET Core中的 JavaScript 函式呼叫 .NET 方法, Blazor 涵蓋從 JavaScript串流至 .NET 的反向作業。

Blazor ASP.NET Core檔案下載涵蓋如何在 中 Blazor 下載檔案。

攔截 JavaScript 例外狀況

若要攔截 JS 例外狀況,請將 JS Interop 包裝在 區塊中try-catch並攔截 JSException

在下列範例中,函 nonFunctionJS 式不存在。 找不到函式時, JSException 會截獲 ,並 Message 指出下列錯誤:

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

Pages/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 函式

JS使用AbortController搭配 CancellationTokenSource 元件中的 ,從 C# 程式碼中止長時間執行的 JavaScript 函式。

下列 JSHelpers 類別包含模擬長時間執行的函式, longRunningFn 可持續計算,直到 AbortController.signalAbortController.abort 指出已呼叫為止。 此 sleep 函式是為了示範目的,以模擬長時間執行的函式執行速度緩慢,而且不會出現在生產程式碼中。 當元件呼叫 stopFn 時,會 longRunningFn 透過 上的 AbortSignal.aborted 迴圈條件式檢查發出中止的 while 訊號。

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

下列 CallJsExample12 元件:

Pages/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!

其他資源

如需如何從 JS 呼叫 .NET 方法的詳細資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

若要從 .NET 呼叫 , JS 請插入 IJSRuntime 抽象概念,並呼叫下列其中一種方法:

針對上述叫 JS 用函式的 .NET 方法:

  • 函式識別碼 (String) 相對於全域範圍 (window) 。 若要呼叫 window.someScope.someFunction ,識別碼為 someScope.someFunction 。 在呼叫函式之前,不需要先註冊函式。
  • 將任意數目的 JS ON 序列化引數 Object[] 傳遞至函 JS 式。
  • 取消權杖 (CancellationToken) 傳播應取消作業的通知。
  • TimeSpan 表示作業的時間限制 JS 。
  • TValue 回型別也必須是 JS ON 可序列化。 TValue 應該符合最能對應至傳回 ON JS 類型的 .NET 類型。
  • JS Promise方法會傳 InvokeAsync 回 。 InvokeAsync 解除包裝 Promise ,並傳回 所 Promise 等候的值。

針對 Blazor 已啟用預先呈現的應用程式,在預先呈現期間無法呼叫 JS 。 如需詳細資訊,請參閱 Prerendering 一 節。

下列範例是以 TextDecoder , JS 為基底解碼器。 此範例示範如何從 C# 方法叫用函 JS 式,以將開發人員程式碼的需求卸載至現有的 JS API。 函 JS 式會接受來自 C# 方法的位元組陣列、解碼陣列,並將文字傳回元件以供顯示。

在 () 或 () 結尾 </body> 標記 wwwroot/index.html 內新增下列 JS 程式碼: Blazor ServerPages/_Host.cshtmlBlazor WebAssembly

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

下列 CallJsExample1 元件:

  • convertArrayJS 選取按鈕 Convert Array 時 () 叫用 InvokeAsync 函式。
  • 呼叫函 JS 式之後,傳遞的陣列會轉換成字串。 字串會傳回給元件,以顯示 (text) 。

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

JavaScript API 受限於使用者手勢

本節僅適用于 Blazor Server 應用程式。

某些瀏覽器 JavaScript (JS) API 只能在使用者手勢的內容中執行,例如使用Fullscreen API (MDN 檔) 。 這些 API 無法透過 JS 應用程式中的 Interop 機制呼叫,因為 UI 事件處理是以非同步方式 Blazor Server 執行,而且通常不再在使用者手勢的內容中執行。 應用程式必須在 JavaScript 中完全處理 UI 事件,因此請使用 onclick 而非 Blazor 的 @onclick 指示詞屬性。

叫用 JavaScript 函式而不讀取傳回的值 () InvokeVoidAsync

使用 InvokeVoidAsync 時機:

在 () 或 () Blazor Server 的結尾 </body> 標籤 Blazor WebAssemblywwwroot/index.html 內,提供函 JSdisplayTickerAlert1 式。 Pages/_Host.cshtml 函式會使用 呼叫 InvokeVoidAsync ,且不會傳回值:

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

元件 (.razor) 範例 (InvokeVoidAsync)

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample2 元件中的 方法。

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

類別 (.cs) 範例 (InvokeVoidAsync)

JsInteropClasses1.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample3 元件中的 方法。

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

叫用 JavaScript 函式並讀取傳回的值 (InvokeAsync)

當 .NET 應該讀取呼叫的結果 JS 時,請使用 InvokeAsync

在 () 或 () Blazor Server 的結尾 </body> 標籤 Blazor WebAssemblywwwroot/index.html 內,提供函 JSdisplayTickerAlert2 式。 Pages/_Host.cshtml 下列範例會傳回字串,供呼叫端顯示:

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

元件 (.razor) 範例 (InvokeAsync)

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample4 元件中顯示傳回的字串。

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

類別 (.cs) 範例 (InvokeAsync)

JsInteropClasses2.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample5 元件中顯示傳回的字串。

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

動態內容產生案例

若要使用 BuildRenderTree產生動態內容,請使用 [Inject] 屬性:

[Inject]
IJSRuntime JS { get; set; }

預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的 裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript () JS 等特定動作。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式是使用 JSRuntimeExtensions.InvokeVoidAsync 呼叫,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下不建議直接修改 DOM JS ,因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

OnAfterRender{Async}伺服器上的預先呈現程式期間,不會呼叫生命週期事件。 OnAfterRender{Async}覆寫 方法以延遲 JS Interop 呼叫,直到在預先呈現元件之後,並在用戶端上以互動方式呈現為止。

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

注意

上述範例會使用全域方法來叫用用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

下列元件示範如何使用 JS Interop 作為元件初始化邏輯的一部分,以與預先呈現相容的方式。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下不建議直接修改 DOM JS ,因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 時 JSRuntime.InvokeAsyncElementReference 只會在 和 之前的任何生命週期方法中使用 OnAfterRenderAsync ,因為直到轉譯元件之後,才會有 JS 元素。

StateHasChanged會呼叫 以重新呈現元件,其中包含從 JS interop 呼叫 (取得的新狀態,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 為 nulldata 才會呼叫 。

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

注意

上述範例會使用全域方法來叫用用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

應用程式中的 Blazor WebAssembly 同步 JS Interop

本節僅適用于 Blazor WebAssembly 應用程式。

不論所呼叫的程式碼是同步還是非同步,JS Interop 呼叫預設都是非同步。 根據預設,呼叫為非同步,以確保元件在 Blazor 裝載模型 (Blazor Server 和 Blazor WebAssembly) 之間都相容。 在 上 Blazor Server ,所有 JS Interop 呼叫都必須是非同步,因為它們是透過網路連線傳送的。

如果您知道您的應用程式只會在 上 Blazor WebAssembly 執行,您可以選擇進行同步 JS Interop 呼叫。 這比進行非同步呼叫稍微少一些額外負荷,而且可能會導致轉譯週期較少,因為等候結果時沒有中繼狀態。

若要在 Blazor WebAssembly 應用程式中從 .NET 同步呼叫 JavaScript,請轉換成 IJSRuntimeIJSInProcessRuntime 以進行 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 或更新版本中 Blazor WebAssembly 使用 IJSObjectReference 時,您可以改為以同步方式使用 IJSInProcessObjectReference

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

JavaScript 的位置

使用JavaScript () 互通性 (互通性) 概觀一文所述,載入 JavaScript (JS) 程式碼: JS

如需在模組中JS隔離腳本的詳細資訊,請參閱JavaScript 模組中的 JavaScript 隔離一節。

警告

請勿將 <script> 標記放在元件檔案中 (.razor) , <script> 因為無法動態更新標記。

JavaScript 模組中的 JavaScript 隔離

Blazor 會啟用標準 JavaScript 模組 (ECMAScript 規格) 中的 JavaScript (JS) 隔離。

JS 隔離提供下列優點:

  • 已匯入的 JS 不再會產生全域命名空間問題。
  • 不需要程式庫和元件的取用者即可匯入相關 JS。

例如,下列 JS 模組會匯出 JS 用於顯示 瀏覽器視窗提示的函式。 將下列 JS 程式碼放在外部 JS 檔案中。

wwwroot/scripts.js:

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

將上述 JS 模組新增至應用程式或類別庫作為資料夾中的靜態 Web 資產 wwwroot ,然後在 實例上 IJSRuntime 呼叫 InvokeAsync ,將模組匯入 .NET 程式碼。

IJSRuntime 會將模組匯入為 IJSObjectReference ,代表來自 .NET 程式碼之 物件的參考 JS 。 IJSObjectReference使用 從模組叫用匯出的 JS 函式。

Pages/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 FILENAME (.js)} ,其中:
    • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
    • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。

動態匯入模組需要網路要求,因此只能藉由呼叫 InvokeAsync 以非同步方式達成。

IJSInProcessObjectReference 表示物件的參考, JS 其函式可以在應用程式中同步 Blazor WebAssembly 叫用。 如需詳細資訊,請參閱應用程式中的 Blazor WebAssembly 同步 JS Interop一節。

注意

類別庫提供 Razor外部 JS 檔案時,請使用其穩定的靜態 Web 資產路徑來指定模組的 JS 檔案: : ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
  • {PACKAGE ID} 預留位置是程式庫的封裝識別碼。 如果未在專案檔中指定 <PackageId>,則封裝識別碼預設為專案的組件名稱。 在下列範例中,程式庫的元件名稱是 ComponentLibrary ,而且程式庫的專案檔未指定 <PackageId>
  • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。 在下列範例中 JS ,外部檔案 () script.js 放在類別庫的 wwwroot 資料夾中。
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

如需詳細資訊,請參閱從 Razor 類別庫 (RCL) 取用 ASP.NET Core Razor

擷取元素的參考

某些 JavaScript () JS Interop 案例需要 HTML 元素的參考。 例如,UI 程式庫可能需要元素參考進行初始化,或者您可能需要在 或 等 clickplay 專案上呼叫類似命令的 API。

使用下列方法擷取元件中 HTML 元素的參考:

  • @ref將屬性新增至 HTML 專案。
  • 定義類型的 ElementReference 欄位,其名稱符合屬性的值 @ref

下列範例示範擷取元素的 username<input> 參考:

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

@code {
    private ElementReference username;
}

警告

只使用元素參考來變動未與 Blazor 互動之空白元素的內容。 當協力廠商 API 提供內容給 元素時,此案例很有用。 因為 Blazor 不會與元素互動,所以在 元素的表示與 Document Object Model (DOM) 之間 Blazor 沒有衝突的可能性。

在下列範例中,將未排序清單的內容 (ul) 變動會很危險,因為 Blazor 與 DOM 互動,以從 Todos 物件 () 填入這個專案的清單專案 <li>

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

如果 JS Interop 會變動元素 MyList 的內容,並 Blazor 嘗試將差異套用至元素,則差異不會與 DOM 相符。

如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

ElementReference 透過 JS Interop 傳遞至 JS 程式碼。 程式 JS 代碼會 HTMLElement 接收 實例,其可與一般 DOM API 搭配使用。 例如,下列程式碼會定義 .NET 擴充方法 (TriggerClickEvent) ,以便將滑鼠點選傳送至專案。

函 JS 式 clickElement 會在 click 傳遞的 HTML 專案上建立事件, (element) :

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

若要呼叫 JS 未傳回值的函式,請使用 JSRuntimeExtensions.InvokeVoidAsync 。 下列程式碼會呼叫 JS 上述函式並擷取 ElementReference ,以觸發用戶端 click 事件:

@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 。 若要在元件完成轉譯之後操作元素參考,請使用OnAfterRenderAsyncOnAfterRender 元件生命週期方法

使用泛型型別並傳回值時,請使用 ValueTask<TResult>

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

{JAVASCRIPT FUNCTION}預留位置是函 JS 式識別碼。

GenericMethod 直接在具有 型別的 物件上呼叫 。 下列範例假設 GenericMethod 可從 JsInteropClasses 命名空間取得 :

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

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

跨元件參考元素

ElementReference無法在元件之間傳遞,因為:

若要讓父元件讓元素參考可供其他元件使用,父元件可以:

  • 允許子元件註冊回呼。
  • 使用傳遞的專案參考在 OnAfterRender 事件期間叫用已註冊的回呼。 間接地,此方法可讓子元件與父元素參考互動。

將下列樣式新增至 <head>wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) :

<style>
    .red { color: red }
</style>

在結尾標籤 wwwroot/index.html 內新增下列腳本 </body> , (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) :

<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Pages/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?" />

Pages/CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的元件 Pages 。 如果在本機測試程式碼,請更新 命名空間。

Shared/SurveyPrompt.razor (子元件) :

@inject IJSRuntime JS

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

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

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

        [Parameter]
        public IObservable<ElementReference> Parent { 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 呼叫

本節主要適用于 Blazor Server 應用程式,但 Blazor WebAssembly 如果條件保證,應用程式也可能設定 JS Interop 逾時。

在 Blazor Server 應用程式中,JavaScript (JS) Interop 可能會因為網路錯誤而失敗,而且應該視為不可靠。 根據預設, Blazor Server 應用程式會針對 JS Interop 呼叫使用一分鐘逾時。 如果應用程式可以容許更積極逾時,請使用下列其中一種方法來設定逾時。

使用 在 的 方法 Startup.csCircuitOptions.JSInteropDefaultCallTimeoutStartup.ConfigureServices 設定全域逾時:

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

{TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)

在元件程式碼中設定每個調用逾時。 指定的逾時會覆寫 所 JSInteropDefaultCallTimeout 設定的全域逾時:

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

在上述範例中:

  • {TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)
  • {ID}預留位置是要叫用之函式的識別碼。 例如,值 someScope.someFunction 會叫用 函式 window.someScope.someFunction

雖然 Interop 失敗的常見原因是 JS 應用程式中的網路失敗 Blazor Server ,但可以針對應用程式中的 Interop 呼叫 Blazor WebAssembly 設定每個叫用 JS 逾時。 雖然應用程式中沒有任何 SignalR 線路存在, JS 但 Interop 呼叫可能會因為應用程式中套用 Blazor WebAssembly 的其他 Blazor WebAssembly 原因而失敗。

如需資源耗盡的詳細資訊,請參閱ASP.NET Core Blazor Server 的威脅防護指引

避免迴圈物件參考

包含迴圈參考的物件無法在用戶端上序列化下列其中一項:

  • .NET 方法呼叫。
  • 當傳回型別具有迴圈參考時,JavaScript 方法會從 C# 呼叫。

轉譯 UI 的 JavaScript 程式庫

有時候,您可能會想要使用 JavaScript (JS) 程式庫,在瀏覽器 Document Object Model (DOM) 內產生可見的使用者介面元素。 第一眼,這看起來可能很困難,因為 Blazor 差異系統依賴控制 DOM 元素的樹狀結構,並在某些外部程式碼變動 DOM 樹狀結構並使其套用差異的機制失效時發生錯誤。 這不是特定的 Blazor 限制。 任何差異型 UI 架構都會發生相同的挑戰。

幸運的是,在元件 UI 中可靠地內 Razor 嵌外部產生的 UI 相當簡單。 建議的技術是讓元件的程式碼 (.razor 檔案產生空白元素) 。 Blazor就差異系統而言,元素一律是空的,因此轉譯器不會遞迴到元素中,而是將其內容單獨保留。 這可讓您安全地以任意外部管理的內容填入元素。

下列範例示範概念。 當 為 truefirstRender ,在 if 語句中,使用 unmanagedElement Interop 與 外部 BlazorJS 互動。 例如,呼叫外部連結 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 類別庫取得。

注意

若要建立 Mapbox 對應,請從 Mapbox 登入 取得存取權杖,並提供它出現在下列程式碼中的位置 {ACCESS TOKEN}

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

若要產生正確的樣式,請將下列樣式表單標籤新增至主機 HTML 頁面。

wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中,將下列 <link> 專案新增至 <head> 專案標記:

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

Pages/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)
        => await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, latitude, 
            longitude).AsTask();

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

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

上述範例會產生互動式地圖 UI。 使用者:

  • 可以拖曳以捲動或縮放。
  • 選取按鈕以跳至預先定義的位置。

日本的 Mapbox 街道地圖,其中具有選取 B,英國和東京、日本的按鈕

在上述範例中:

  • <div>@ref="mapElement" 保持空白,就 Blazor 如同所考慮。 腳本 mapbox-gl.js 可以安全地填入元素,並修改其內容。 將這項技術與轉譯 UI 的任何 JS 程式庫搭配使用。 只要元件未嘗試連絡並修改頁面的其他部分,您就可以從協力廠商 JS SPA 架構內 Razor 嵌元件。 外部程式 JS 代碼修改不視為空白的專案 Blazor 並不安全。
  • 使用此方法時,請記住如何 Blazor 保留或終結 DOM 元素的規則。 元件會安全地處理按鈕點選事件,並更新現有的對應實例,因為預設會保留 DOM 元素。 如果您要從迴圈內 @foreach 轉譯對應專案清單,則想要使用 @key 來確保元件實例的保留。 否則,清單資料的變更可能會導致元件實例以不想要的方式保留先前實例的狀態。 如需詳細資訊,請參閱using @key to preserve elements and components
  • 此範例會在 JS ES6 模組中封裝邏輯和相依性,並使用 import 識別碼動態載入模組。 如需詳細資訊,請參閱 JavaScript 模組中的 JavaScript 隔離

JavaScript Interop 呼叫的大小限制

本節僅適用于 Blazor Server 應用程式。 在 中 Blazor WebAssembly ,架構不會限制 JavaScript () JS Interop 輸入和輸出的大小。

在 中 Blazor Server , JS Interop 呼叫的大小受限於中樞方法允許的傳入 SignalR 訊息大小上限,預設會 HubOptions.MaximumReceiveMessageSize 強制執行 (:32 KB) 。 JS 大於擲回錯誤的 .NET SignalR 訊息 MaximumReceiveMessageSize 。 架構不會限制從中樞到用戶端的 SignalR 訊息大小。

當記錄未設定為 [錯] 或 [追蹤]時 SignalR ,訊息大小錯誤只會出現在瀏覽器的開發人員工具主控台中:

錯誤:連線中斷連線時發生錯誤「錯誤:伺服器在關閉時傳回錯誤:連線已關閉併發生錯誤。」。

當伺服器端記錄設定為 [偵錯] 或 [追蹤]SignalR,伺服器端記錄會顯示 InvalidDataException 訊息大小錯誤的 。

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

錯誤:

System.IO.InvalidDataException:超過訊息大小上限 32768B。 您可以在 AddHubOptions 中設定訊息大小。

在 中 Startup.ConfigureServices 設定 MaximumReceiveMessageSize ,以增加限制:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR增加傳入訊息大小限制是需要更多伺服器資源的成本,而且會公開伺服器,以增加惡意使用者的風險。 此外,將大量內容讀入記憶體做為字串或位元組陣列,也會導致使用垃圾收集行程不佳的配置,因而造成額外的效能負面影響。

讀取大型承載的其中一個選項是以較小的區區塊轉送內容,並將承載處理為 Stream 。 當讀取大型 JS ON 承載時,或當資料以原始位元組的形式提供 JS 時,可以使用這個方法。 如需示範在 中 Blazor Server 傳送大型二進位承載的範例,其使用與InputFile 元件類似的技術,請參閱二進位提交範例應用程式

注意

.NET 參考來源的文件連結通常會載入存放庫的預設分支,這表示下一版 .NET 的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

開發在應用程式中 BlazorBlazor Server 傳輸大量資料 JS 的程式碼時,請考慮下列指引:

  • 將資料分割成較小的片段,並依序傳送資料區段,直到伺服器收到所有資料為止。
  • 請勿在 和 C# 程式碼中 JS 配置大型物件。
  • 傳送或接收資料時,請勿長時間封鎖主要 UI 執行緒。
  • 當進程完成或取消時,釋放已耗用的記憶體。
  • 基於安全性目的,強制執行下列額外需求:
    • 宣告可以傳遞的檔案或資料大小上限。
    • 宣告從用戶端到伺服器的最小上傳速率。
  • 伺服器收到資料之後,資料可以是:
    • 暫時儲存在記憶體緩衝區中,直到收集所有區段為止。
    • 立即取用。 例如,資料可以立即儲存在資料庫中,或在收到每個區段時寫入磁片。

Unmarshalled JavaScript Interop

Blazor WebAssembly 當 .NET 物件序列化為 JavaScript () JS Interop 且下列任一項成立時,元件可能會遇到效能不佳的情況:

  • 大量 .NET 物件會快速序列化。 例如,當 Interop 呼叫是以移動輸入裝置為基礎進行時 JS ,效能不佳,例如旋轉滑鼠滾輪。
  • 大型 .NET 物件或許多 .NET 物件必須針對 Interop 序列化 JS 。 例如,當 Interop 呼叫需要序列化數十個檔案時 JS ,效能不佳可能會導致。

IJSUnmarshalledObjectReference 表示可以叫用其函式的物件參考 JS ,而不需要序列化 .NET 資料的額外負荷。

在下例中︰

  • 包含字串和整數 的結構 會傳遞至 未序列化為 JS 。
  • JS 函式會處理資料,並將布林值或字串傳回給呼叫端。
  • JS字串無法直接轉換成 .NET string 物件。 函 unmarshalledFunctionReturnString 式會呼叫 BINDING.js_string_to_mono_string 來管理字串的 JS 轉換。

注意

下列範例不是此案例的典型使用 案例,因為 傳遞至 JS 的結構不會造成元件效能不佳。 此範例只會使用小型物件來示範傳遞未序列化 .NET 資料的概念。

將下列 <script> 區塊放在 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml () Blazor Server 。 或者,您可以將 放在 JS 結尾 <script src="{SCRIPT PATH AND FILE NAME (.js)}></script> 標籤內 </body> 參考的外部 JS 檔案中,其中 {SCRIPT PATH AND FILE NAME (.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_string_to_mono_string 式名稱、行為和存在可能會在未來的 .NET 版本中變更。 例如:

  • 函式可能會重新命名。
  • 函式本身可能會遭到移除,而偏好由架構自動轉換字串。

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

IJSUnmarshalledObjectReference如果實例未在 C# 程式碼中處置,則可以在 中 JS 處置。 從 呼叫 JS 時,下列 dispose 函式會處置物件參考:

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

    ...
  };
}

陣列類型可以使用 從 JS 物件轉換成 .NET 物件 js_typed_array_to_array ,但 JS 陣列必須是具型別陣列。 的 JS 陣列可以在 C# 程式碼中讀取為 .NET 物件陣列, (object[]) 。

您可以轉換其他資料類型,例如字串陣列,但需要建立新的 Mono 陣列物件, (mono_obj_array_new) 並設定其值 (mono_obj_array_set) 。

警告

JS 架構所提供的 Blazor 函式,例如 js_typed_array_to_arraymono_obj_array_newmono_obj_array_set ,會受限於名稱變更、行為變更,或在未來的 .NET 版本中移除。

攔截 JavaScript 例外狀況

若要攔截 JS 例外狀況,請將 JS Interop 包裝在 區塊中try-catch並攔截 JSException

在下列範例中,函 nonFunctionJS 式不存在。 找不到函式時, JSException 會截獲 Message 指出下列錯誤的 :

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

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

其他資源

如需如何從 JS 呼叫 .NET 方法的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

若要從 .NET 呼叫 , JS 請插入 IJSRuntime 抽象概念,並呼叫下列其中一種方法:

針對叫用 JS 函式的上述 .NET 方法:

  • 函式識別碼 (String) 相對於全域範圍 (window) 。 若要呼叫 window.someScope.someFunction ,識別碼為 someScope.someFunction 。 在呼叫函式之前,不需要先註冊函式。
  • 將 中 Object[] 任意數目的 JS ON 可序列化引數傳遞至函 JS 式。
  • 解除標記 (CancellationToken) 傳播應取消作業的通知。
  • TimeSpan 表示作業的時間限制 JS 。
  • TValue 回型別也必須是 JS ON 可序列化。 TValue 應該符合最適合對應至 JS 所傳回 ON 類型的 .NET 類型。
  • 方法 JS Promise 會傳 InvokeAsync 回 。 InvokeAsync 解除包裝 Promise ,並傳回 等候 Promise 的值。

針對 Blazor 已啟用預先呈現的應用程式,在預先呈現期間無法呼叫 JS 。 如需詳細資訊,請參閱 Prerendering 一節。

下列範例是以 TextDecoder 為基礎,以 JS 解碼器為基礎。 此範例示範如何從 C# 方法叫 JS 用函式,將需求從開發人員程式碼卸載至現有的 JS API。 函 JS 式會接受來自 C# 方法的位元組陣列、解碼陣列,並將文字傳回給元件以供顯示。

在 (的結尾 </body> 標籤 wwwroot/index.htmlBlazor WebAssembly 內新增下列 JS 程式碼,) 或 Pages/_Host.cshtml (Blazor Server) :

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

下列 CallJsExample1 元件:

  • convertArrayJS 選取按鈕 () Convert Array 時叫用 函 InvokeAsync 式。
  • 呼叫函 JS 式之後,傳遞的陣列會轉換成字串。 字串會傳回給元件,以顯示 (text) 。

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

本節僅適用于 Blazor Server 應用程式。

某些瀏覽器 JavaScript (JS) API 只能在使用者手勢的內容中執行,例如使用Fullscreen API (MDN 檔) 。 這些 API 無法透過應用程式中的 JS Interop 機制 Blazor Server 呼叫,因為 UI 事件處理是以非同步方式執行,而且通常不再于使用者手勢的內容中。 應用程式必須在 JavaScript 中完全處理 UI 事件,因此請使用 onclick 而不是 Blazor 的 @onclick 指示詞屬性。

叫用 JavaScript 函式,而不讀取傳回的值 (InvokeVoidAsync)

使用 InvokeVoidAsync 時機:

在 () 或 () 的 wwwroot/index.html 結尾 </body> 標記內,提供函 JSdisplayTickerAlert1 式。 Blazor ServerPages/_Host.cshtmlBlazor WebAssembly 函式會使用 InvokeVoidAsync 呼叫,而且不會傳回值:

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

元件 (.razor) 範例 (InvokeVoidAsync)

TickerChangedhandleTickerChanged1會呼叫下列 CallJsExample2 元件中的 方法。

Pages/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 System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged1會呼叫下列 CallJsExample3 元件中的 方法。

Pages/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 應該讀取呼叫的結果時, JS 請使用 InvokeAsync

在 () 或 () 的 wwwroot/index.html 結尾 </body> 標記內,提供函 JSdisplayTickerAlert2 式。 Blazor ServerPages/_Host.cshtmlBlazor WebAssembly 下列範例會傳回字串,供呼叫端顯示:

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

元件 (.razor) 範例 (InvokeAsync)

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample4 元件中顯示傳回的字串。

Pages/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 System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample5 元件中顯示傳回的字串。

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

預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript (JS) 等特定動作。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式會使用 呼叫 JSRuntimeExtensions.InvokeVoidAsync ,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

OnAfterRender{Async}伺服器上的預先呈現程式期間,不會呼叫生命週期事件。 OnAfterRender{Async}覆寫 方法,以延遲 JS Interop 呼叫,直到在預先呈現元件後於用戶端上呈現和互動式為止。

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

下列元件示範如何使用 JS Interop 做為元件初始化邏輯的一部分,以與預先呈現相容的方式使用。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 的位置 JSRuntime.InvokeAsync 時, ElementReference 只會在任何先前的生命週期方法中使用 OnAfterRenderAsync ,因為在轉譯元件之後,沒有 JS 元素。

StateHasChanged會呼叫 以使用從 JS Interop 呼叫 (取得的新狀態來重新呈現元件,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 是 nulldata 才會呼叫。

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

應用程式中的 Blazor WebAssembly 同步 JS Interop

本節僅適用于 Blazor WebAssembly 應用程式。

不論所呼叫的程式碼是同步還是非同步,JS Interop 呼叫預設都是非同步。 根據預設,呼叫為非同步,以確保元件在 Blazor 裝載模型 (Blazor Server 和 Blazor WebAssembly) 之間都相容。 在 上 Blazor Server ,所有 JS Interop 呼叫都必須是非同步,因為它們是透過網路連線傳送的。

如果您知道您的應用程式只在 上 Blazor WebAssembly 執行,您可以選擇進行同步 JS Interop 呼叫。 這比進行非同步呼叫稍微少一些額外負荷,而且可能會導致轉譯週期較少,因為等候結果時沒有中繼狀態。

若要在應用程式中從 .NET 同步呼叫 JavaScript Blazor WebAssembly ,請轉換成 IJSRuntimeIJSInProcessRuntime 以進行 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 或更新版本中 Blazor WebAssembly 使用 IJSObjectReference 時,您可以改為同步使用 IJSInProcessObjectReference

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

JavaScript 的位置

使用 JavaScript (JS) 互通性 (interop) 概觀一文中所述的任何方法載入 JavaScript JS () 程式碼:

警告

請勿將 <script> 標籤放在元件檔案 () .razor ,因為 <script> 無法動態更新標籤。

擷取元素的參考

某些 JavaScript (JS) Interop 案例需要 HTML 元素的參考。 例如,UI 程式庫可能需要元素參考以進行初始化,或者您可能需要在 元素上呼叫類似命令的 API,例如 clickplay

使用下列方法擷取元件中 HTML 元素的參考:

  • @ref將屬性新增至 HTML 專案。
  • 定義型 ElementReference 別的欄位,其名稱符合屬性的值 @ref

下列範例顯示擷取專案的 username<input> 參考:

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

@code {
    private ElementReference username;
}

警告

只使用元素參考來變動未與 Blazor 互動之空白元素的內容。 當協力廠商 API 提供內容給 元素時,此案例很有用。 因為 Blazor 不會與元素互動,所以在元素的標記法與檔物件模型之間 Blazor 不可能發生衝突 (DOM) 。

在下列範例中,將未排序清單的內容變動 (ul) 是危險的,因為 Blazor 與 DOM 互動,以從 Todos 物件 () 填入此元素的清單專案 <li>

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

如果 JS Interop 會變動元素 MyList 的內容,並 Blazor 嘗試將差異套用至元素,差異就不會與 DOM 相符。

如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

ElementReference會透過 Interop 傳遞至 JS 程式 JS 代碼。 程式 JS 代碼會 HTMLElement 接收實例,其可與一般 DOM API 搭配使用。 例如,下列程式碼會定義 .NET 擴充方法 (TriggerClickEvent) ,以便將滑鼠按一下傳送至元素。

函 JS 式 clickElement 會在 click 傳遞的 HTML 元素上建立事件, (element) :

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

若要呼叫 JS 未傳回值的函式,請使用 JSRuntimeExtensions.InvokeVoidAsync 。 下列程式碼會藉由使用擷 ElementReference 取的 呼叫上述 JS 函式來觸發用戶端 click 事件:

@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 。 若要在元件完成轉譯之後操作專案參考,請使用OnAfterRenderAsyncOnAfterRender 元件生命週期方法

使用泛型型別並傳回值時,請使用 ValueTask<TResult>

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

{JAVASCRIPT FUNCTION} 位符是函 JS 式識別碼。

GenericMethod 直接在具有型別的 物件上呼叫 。 下列範例假設 GenericMethod 可從 命名空間取得 JsInteropClasses

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

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

跨元件參考元素

ElementReference無法在元件之間傳遞,因為:

若要讓父元件讓專案參考可供其他元件使用,父元件可以:

  • 允許子元件註冊回呼。
  • 使用傳遞的專案參考, OnAfterRender 在事件期間叫用已註冊的回呼。 此方法間接可讓子元件與父元素參考互動。

將下列樣式新增至 <head>wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml () Blazor Server :

<style>
    .red { color: red }
</style>

在結尾標籤 wwwroot/index.html 內新增下列腳本 </body> , (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) :

<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Pages/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?" />

Pages/CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的元件 Pages 。 如果在本機測試程式碼,請更新 命名空間。

Shared/SurveyPrompt.razor (子元件) :

@inject IJSRuntime JS

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

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

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

        [Parameter]
        public IObservable<ElementReference> Parent { 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 呼叫

本節主要適用于 Blazor Server 應用程式,但 Blazor WebAssembly 如果條件保證,應用程式也可能設定 JS Interop 逾時。

在 Blazor Server 應用程式中,JavaScript (JS) Interop 可能會因為網路錯誤而失敗,而且應該視為不可靠。 根據預設, Blazor Server 應用程式會針對 JS Interop 呼叫使用一分鐘逾時。 如果應用程式可以容許更積極逾時,請使用下列其中一種方法來設定逾時。

使用 在 的 方法 Startup.csCircuitOptions.JSInteropDefaultCallTimeoutStartup.ConfigureServices 設定全域逾時:

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

{TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)

在元件程式碼中設定每個調用逾時。 指定的逾時會覆寫 所 JSInteropDefaultCallTimeout 設定的全域逾時:

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

在上述範例中:

  • {TIMEOUT} 位符是 TimeSpan (,例如) TimeSpan.FromSeconds(80)
  • {ID}預留位置是要叫用之函式的識別碼。 例如,值 someScope.someFunction 會叫用 函式 window.someScope.someFunction

雖然 Interop 失敗的常見原因是 JS 應用程式中的網路失敗 Blazor Server ,但可以針對應用程式中的 Interop 呼叫 Blazor WebAssembly 設定每個叫用 JS 逾時。 雖然應用程式中沒有任何 SignalR 線路存在, JS 但 Interop 呼叫可能會因為應用程式中套用 Blazor WebAssembly 的其他 Blazor WebAssembly 原因而失敗。

如需資源耗盡的詳細資訊,請參閱ASP.NET Core Blazor Server 的威脅防護指引

避免迴圈物件參考

包含迴圈參考的物件無法在用戶端上序列化下列其中一項:

  • .NET 方法呼叫。
  • 當傳回型別具有迴圈參考時,JavaScript 方法會從 C# 呼叫。

JavaScript Interop 呼叫的大小限制

本節僅適用于 Blazor Server 應用程式。 在 中 Blazor WebAssembly ,架構不會限制 JavaScript () JS Interop 輸入和輸出的大小。

在 中 Blazor Server , JS Interop 呼叫的大小受限於中樞方法允許的傳入 SignalR 訊息大小上限,預設會 HubOptions.MaximumReceiveMessageSize 強制執行 (:32 KB) 。 JS 大於擲回錯誤的 .NET SignalR 訊息 MaximumReceiveMessageSize 。 架構不會限制從中樞到用戶端的 SignalR 訊息大小。

當記錄未設定為 [錯] 或 [追蹤]時 SignalR ,訊息大小錯誤只會出現在瀏覽器的開發人員工具主控台中:

錯誤:連線中斷連線時發生錯誤「錯誤:伺服器在關閉時傳回錯誤:連線已關閉併發生錯誤。」。

當伺服器端記錄設定為 [偵錯] 或 [追蹤]SignalR,伺服器端記錄會顯示 InvalidDataException 訊息大小錯誤的 。

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

錯誤:

System.IO.InvalidDataException:超過訊息大小上限 32768B。 您可以在 AddHubOptions 中設定訊息大小。

在 中 Startup.ConfigureServices 設定 MaximumReceiveMessageSize ,以增加限制:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR增加傳入訊息大小限制是需要更多伺服器資源的成本,而且會公開伺服器,以增加惡意使用者的風險。 此外,將大量內容讀入記憶體做為字串或位元組陣列,也會導致使用垃圾收集行程不佳的配置,因而造成額外的效能負面影響。

讀取大型承載的其中一個選項是以較小的區區塊轉送內容,並將承載處理為 Stream 。 當讀取大型 JS ON 承載時,或當資料以原始位元組的形式提供 JS 時,可以使用這個方法。 如需示範在 中 Blazor Server 傳送大型二進位承載的範例,其使用與InputFile 元件類似的技術,請參閱二進位提交範例應用程式

注意

.NET 參考來源的文件連結通常會載入存放庫的預設分支,這表示下一版 .NET 的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

開發在應用程式中 BlazorBlazor Server 傳輸大量資料 JS 的程式碼時,請考慮下列指引:

  • 將資料分割成較小的片段,並依序傳送資料區段,直到伺服器收到所有資料為止。
  • 請勿在 和 C# 程式碼中 JS 配置大型物件。
  • 傳送或接收資料時,請勿長時間封鎖主要 UI 執行緒。
  • 當進程完成或取消時,釋放已耗用的記憶體。
  • 基於安全性目的,強制執行下列額外需求:
    • 宣告可以傳遞的檔案或資料大小上限。
    • 宣告從用戶端到伺服器的最小上傳速率。
  • 伺服器收到資料之後,資料可以是:
    • 暫時儲存在記憶體緩衝區中,直到收集所有區段為止。
    • 立即取用。 例如,資料可以立即儲存在資料庫中,或在收到每個區段時寫入磁片。

攔截 JavaScript 例外狀況

若要攔截 JS 例外狀況,請將 JS Interop 包裝在 區塊中try-catch並攔截 JSException

在下列範例中,函 nonFunctionJS 式不存在。 找不到函式時, JSException 會截獲 Message 指出下列錯誤的 :

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

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

其他資源

如需如何從 JS 呼叫 .NET 方法的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

IJSRuntime 是由 Blazor 架構註冊。 若要從 .NET 呼叫 , JS 請插入 IJSRuntime 抽象概念,並呼叫下列其中一種方法:

針對叫用 JS 函式的上述 .NET 方法:

  • 函式識別碼 (String) 相對於全域範圍 (window) 。 若要呼叫 window.someScope.someFunction ,識別碼為 someScope.someFunction 。 在呼叫函式之前,不需要先註冊函式。
  • 將 中 Object[] 任意數目的 JS ON 可序列化引數傳遞至函 JS 式。
  • 解除標記 (CancellationToken) 傳播應取消作業的通知。
  • TimeSpan 表示作業的時間限制 JS 。
  • TValue 回型別也必須是 JS ON 可序列化。 TValue 應該符合最適合對應至 JS 所傳回 ON 類型的 .NET 類型。
  • 方法 JS Promise 會傳 InvokeAsync 回 。 InvokeAsync 解除包裝 Promise ,並傳回 等候 Promise 的值。

針對 Blazor 已啟用預先呈現的應用程式,在預先呈現期間無法呼叫 JS 。 如需詳細資訊,請參閱 Prerendering 一節。

下列範例是以 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);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

下列 CallJsExample1 元件:

  • convertArrayJS 選取按鈕 () Convert Array 時叫用 函 InvokeAsync 式。
  • 呼叫函 JS 式之後,傳遞的陣列會轉換成字串。 字串會傳回給元件,以顯示 (text) 。

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

限制為使用者手勢的 JavaScript API

本節僅適用于 Blazor Server 應用程式。

某些瀏覽器 JavaScript (JS) API 只能在使用者手勢的內容中執行,例如使用Fullscreen API (MDN 檔) 。 這些 API 無法透過應用程式中的 JS Interop 機制 Blazor Server 呼叫,因為 UI 事件處理是以非同步方式執行,而且通常不再于使用者手勢的內容中。 應用程式必須在 JavaScript 中完全處理 UI 事件,因此請使用 onclick 而非 Blazor 的 @onclick 指示詞屬性。

叫用 JavaScript 函式而不讀取傳回的值 () InvokeVoidAsync

使用 InvokeVoidAsync 時機:

提供函式 displayTickerAlert1JS 。 函式會使用 呼叫 InvokeVoidAsync ,且不會傳回值:

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

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

元件 (.razor) 範例 (InvokeVoidAsync)

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample2 元件中的 方法。

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

類別 (.cs) 範例 (InvokeVoidAsync)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged1呼叫下列 CallJsExample3 元件中的 方法。

Pages/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()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

叫用 JavaScript 函式並讀取傳回的值 (InvokeAsync)

InvokeAsync .NET 應該讀取 JavaScript (呼叫 JS 的結果時,請使用) 。

提供函式 displayTickerAlert2JS 。 下列範例會傳回字串,供呼叫端顯示:

<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 互通性 (JS Interop)

元件 (.razor) 範例 (InvokeAsync)

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample4 元件中顯示傳回的字串。

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

類別 (.cs) 範例 (InvokeAsync)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChangedhandleTickerChanged2會呼叫 方法,並在下列 CallJsExample5 元件中顯示傳回的字串。

Pages/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()
    {
        if (jsClass is not null)
        {
            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; }

預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript (JS) 等特定動作。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式會使用 呼叫 JSRuntimeExtensions.InvokeVoidAsync ,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

OnAfterRender{Async}伺服器上的預先呈現程式期間,不會呼叫生命週期事件。 OnAfterRender{Async}覆寫 方法,以延遲 JS Interop 呼叫,直到在預先呈現元件後於用戶端上呈現和互動式為止。

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

下列元件示範如何使用 JS Interop 做為元件初始化邏輯的一部分,以與預先呈現相容的方式使用。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議直接修改 DOM, JS 因為 JS 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 的位置 JSRuntime.InvokeAsync 時, ElementReference 只會在任何先前的生命週期方法中使用 OnAfterRenderAsync ,因為在轉譯元件之後,沒有 JS 元素。

StateHasChanged會呼叫 以使用從 JS Interop 呼叫 (取得的新狀態來重新呈現元件,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 是 nulldata 才會呼叫。

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

注意

上述範例會使用全域方法來壓縮用戶端。 如需生產應用程式中更好的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

範例:

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

應用程式中的 Blazor WebAssembly 同步 JS Interop

本節僅適用于 Blazor WebAssembly 應用程式。

不論所呼叫的程式碼是同步還是非同步,JS Interop 呼叫預設都是非同步。 根據預設,呼叫為非同步,以確保元件在 Blazor 裝載模型 (Blazor Server 和 Blazor WebAssembly) 之間都相容。 在 上 Blazor Server ,所有 JS Interop 呼叫都必須是非同步,因為它們是透過網路連線傳送的。

如果您知道您的應用程式只在 上 Blazor WebAssembly 執行,您可以選擇進行同步 JS Interop 呼叫。 這比進行非同步呼叫稍微少一些額外負荷,而且可能會導致轉譯週期較少,因為等候結果時沒有中繼狀態。

若要在應用程式中從 .NET 同步呼叫 JavaScript Blazor WebAssembly ,請轉換成 IJSRuntimeIJSInProcessRuntime 以進行 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 或更新版本中 Blazor WebAssembly 使用 IJSObjectReference 時,您可以改為同步使用 IJSInProcessObjectReference

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

JavaScript 的位置

使用 JavaScript (JS) 互通性 (interop) 概觀一文中所述的任何方法載入 JavaScript JS () 程式碼:

如需在模組中JS隔離腳本的詳細資訊,請參閱JavaScript 模組中的 JavaScript 隔離一節。

警告

請勿將 <script> 標籤放在元件檔案 () .razor ,因為 <script> 無法動態更新標籤。

JavaScript 模組中的 JavaScript 隔離

Blazor 會啟用標準 JavaScript 模組 (ECMAScript 規格) 中的 JavaScript (JS) 隔離。

JS 隔離提供下列優點:

  • 已匯入的 JS 不再會產生全域命名空間問題。
  • 不需要程式庫和元件的取用者即可匯入相關 JS。

例如,下列 JS 模組會匯出函 JS 式以顯示 瀏覽器視窗提示。 將下列 JS 程式碼放在外部 JS 檔案中。

wwwroot/scripts.js:

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

將上述 JS 模組新增至應用程式或類別庫作為資料夾中的靜態 Web 資產 wwwroot ,然後在 實例上 IJSRuntime 呼叫 InvokeAsync ,將模組匯入 .NET 程式碼。

IJSRuntime 會將模組匯入為 IJSObjectReference ,代表從 .NET 程式碼對 JS 物件的參考。 IJSObjectReference使用 從模組叫用匯出 JS 的函式。

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

在上述範例中:

  • 根據慣例, import 識別碼是特別用於匯入 JS 模組的特殊識別碼。
  • 使用模組的穩定靜態 Web 資產路徑指定模組的外部 JS 檔案:,其中: ./{SCRIPT PATH AND FILENAME (.js)}
    • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
    • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。
  • IJSObjectReference處置 中垃圾收集IAsyncDisposable.DisposeAsync

動態匯入模組需要網路要求,因此只能藉由呼叫 InvokeAsync 來以非同步方式達成。

IJSInProcessObjectReference 表示物件參考 JS ,其函式可以在應用程式中同步 Blazor WebAssembly 叫用。 如需詳細資訊,請參閱 應用程式中的同步 JS Interop 一 Blazor WebAssembly 節。

注意

類別庫提供 Razor外部 JS 檔案時,請使用其穩定的靜態 Web 資產路徑來指定模組的 JS 檔案: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • 需要目前目錄 (./) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。
  • {PACKAGE ID} 預留位置是程式庫的封裝識別碼。 如果未在專案檔中指定 <PackageId>,則封裝識別碼預設為專案的組件名稱。 在下列範例中,程式庫的元件名稱是 ComponentLibrary ,而且程式庫的專案檔未指定 <PackageId>
  • {SCRIPT PATH AND FILENAME (.js)} 預留位置是 wwwroot 下的路徑和檔案名稱。 在下列範例中,外部 JS 檔案 () script.js 放在類別庫的 wwwroot 資料夾中。
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

如需詳細資訊,請參閱從 Razor 類別庫 (RCL) 取用 ASP.NET Core Razor

擷取元素的參考

某些 JavaScript (JS) Interop 案例需要 HTML 元素的參考。 例如,UI 程式庫可能需要元素參考以進行初始化,或者您可能需要在 元素上呼叫類似命令的 API,例如 clickplay

使用下列方法擷取元件中 HTML 元素的參考:

  • @ref將屬性新增至 HTML 專案。
  • 定義型 ElementReference 別的欄位,其名稱符合屬性的值 @ref

下列範例顯示擷取專案的 username<input> 參考:

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

@code {
    private ElementReference username;
}

警告

只使用元素參考來變動未與 Blazor 互動之空白元素的內容。 當協力廠商 API 提供內容給 元素時,此案例很有用。 因為 Blazor 不會與元素互動,所以在元素的標記法與檔物件模型之間 Blazor 不可能發生衝突 (DOM) 。

在下列範例中,將未排序清單的內容變動 (ul) 是危險的,因為 Blazor 與 DOM 互動,以從 Todos 物件 () 填入此元素的清單專案 <li>

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

如果 JS Interop 會變動元素 MyList 的內容,並 Blazor 嘗試將差異套用至元素,差異就不會與 DOM 相符。

如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

ElementReference會透過 Interop 傳遞至 JS 程式 JS 代碼。 程式 JS 代碼會 HTMLElement 接收實例,其可與一般 DOM API 搭配使用。 例如,下列程式碼會定義 .NET 擴充方法 (TriggerClickEvent) ,以便將滑鼠按一下傳送至元素。

函 JS 式 clickElement 會在 click 傳遞的 HTML 元素上建立事件, (element) :

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

若要呼叫 JS 未傳回值的函式,請使用 JSRuntimeExtensions.InvokeVoidAsync 。 下列程式碼會藉由使用擷 ElementReference 取的 呼叫上述 JS 函式來觸發用戶端 click 事件:

@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 。 若要在元件完成轉譯之後操作專案參考,請使用OnAfterRenderAsyncOnAfterRender 元件生命週期方法

使用泛型型別並傳回值時,請使用 ValueTask<TResult>

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

{JAVASCRIPT FUNCTION} 位符是函 JS 式識別碼。

GenericMethod 直接在具有型別的 物件上呼叫 。 下列範例假設 GenericMethod 可從 命名空間取得 JsInteropClasses

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

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

跨元件參考元素

ElementReference無法在元件之間傳遞,因為:

若要讓父元件讓專案參考可供其他元件使用,父元件可以:

  • 允許子元件註冊回呼。
  • 使用傳遞的專案參考, 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 互通性 (JS Interop)

Pages/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?" />

Pages/CallJsExample7.razor.cs:

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

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

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

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

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的元件 Pages 。 如果在本機測試程式碼,請更新命名空間。

Shared/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=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; }
}

在上述範例中,應用程式的命名空間是 BlazorSample 資料夾中的共用元件 Shared 。 如果在本機測試程式碼,請更新命名空間。

強化 JavaScript Interop 呼叫

本節主要適用于 Blazor Server 應用程式,但如果 Blazor WebAssembly 有條件,應用程式也可能設定 JS Interop 逾時。

在 Blazor Server 應用程式中,JavaScript (JS) Interop 可能會因為網路錯誤而失敗,而且應該視為不可靠。 根據預設, Blazor Server 應用程式會針對 JS Interop 呼叫使用一分鐘逾時。 如果應用程式可以容許更積極逾時,請使用下列其中一種方法設定逾時。

使用 CircuitOptions.JSInteropDefaultCallTimeout 設定 中的 Program.cs 全域逾時:

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

{TIMEOUT} 位符是 TimeSpan (, TimeSpan.FromSeconds(80) 例如,) 。

在元件程式碼中設定每個叫用逾時。 指定的逾時會覆寫 所 JSInteropDefaultCallTimeout 設定的全域逾時:

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

在上述範例中:

  • {TIMEOUT} 位符是 TimeSpan (, TimeSpan.FromSeconds(80) 例如,) 。
  • {ID} 位符是要叫用之函式的識別碼。 例如,值 someScope.someFunction 會叫用 函式 window.someScope.someFunction

雖然 Interop 失敗的 JS 常見原因是應用程式中的網路失敗 Blazor Server ,但可以針對 JS 應用程式中的 Blazor WebAssembly Interop 呼叫設定每個叫用逾時。 雖然應用程式中沒有任何 SignalR 線路存在 Blazor WebAssembly , JS 但 Interop 呼叫可能會因為應用程式中套用 Blazor WebAssembly 的其他原因而失敗。

如需資源耗盡的詳細資訊,請參閱ASP.NET Core Blazor Server 的威脅防護指導方針

避免迴圈物件參考

包含迴圈參考的物件無法在用戶端上序列化:.

  • .NET 方法呼叫。
  • 當傳回型別有迴圈參考時,JavaScript 方法會從 C# 呼叫。

轉譯 UI 的 JavaScript 程式庫

有時候,您可能想要使用 JavaScript () JS 程式庫,在瀏覽器檔物件模型內產生可見的使用者介面元素, (DOM) 。 第一眼,這似乎很困難,因為 Blazor 差異系統依賴控制 DOM 元素的樹狀結構,如果某些外部程式碼改變 DOM 樹狀結構,並使其套用差異的機制失效,就會發生錯誤。 Blazor這不是特定的限制。 任何差異型 UI 架構都會發生相同的挑戰。

幸運的是,在元件 UI 中 Razor 可靠地內嵌外部產生的 UI 很簡單。 建議的技術是讓元件的程式碼 (檔案) .razor 產生空元素。 就 Blazor 差異系統而言,元素一律是空的,因此轉譯器不會遞迴至元素,而是單獨保留其內容。 這可讓您放心地以任意外部管理的內容填入元素。

下列範例示範概念。 if當 為 truefirstRender ,在 語句中,使用 unmanagedElementJS Interop 與 外部 Blazor 互動。 例如,呼叫外部連結 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 類別庫取得。

注意

若要建立 Mapbox 對應,請從 Mapbox 登入 取得存取權杖,並提供它出現在下列程式碼中的位置 {ACCESS TOKEN}

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

若要產生正確的樣式,請將下列樣式表單標籤新增至主 HTML 頁面。

將下列 <link> 專案新增至 <head> 專案標記 (內容位置 <head>) :

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

Pages/CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

上述範例會產生互動式地圖 UI。 使用者:

  • 可以拖曳以捲動或縮放。
  • 選取按鈕以跳至預先定義的位置。

日本的 Mapbox 街道地圖,具有按鈕可選取 [B、英國和東京、日本

在上述範例中:

  • @ref="mapElement"<div> 保持空白,就如同 Blazor 考慮一樣。 腳本 mapbox-gl.js 可以安全地填入元素,並修改其內容。 將這項技術與轉譯 UI 的任何 JS 程式庫搭配使用。 只要元件未嘗試連絡並修改頁面的其他部分,您就可以從協力廠商 JS SPA 架構 Razor 內嵌元件。 外部 JS 程式碼修改不視為空白的專案 Blazor 並不安全。
  • 使用此方法時,請記住如何 Blazor 保留或終結 DOM 元素的規則。 元件會安全地處理按鈕按一下事件,並更新現有的對應實例,因為 DOM 元素預設會保留。 如果您要從迴圈內 @foreach 轉譯地圖專案清單,則想要使用 @key 以確保元件實例的保留。 否則,清單資料的變更可能會導致元件實例以不想要的方式保留先前實例的狀態。 如需詳細資訊,請參閱using @key to preserve elements and components
  • 此範例會 JS 封裝 ES6 模組內的邏輯和相依性,並使用識別碼動態 import 載入模組。 如需詳細資訊,請參閱 JavaScript 模組中的 JavaScript 隔離

位元組陣列支援

Blazor 支援優化位元組陣列 JavaScript (JS) Interop,以避免將位元組陣列編碼/解碼為 Base64。 下列範例會使用 JS Interop 將位元組陣列傳遞至 JavaScript。

提供函式 receiveByteArrayJS 。 函式會使用 呼叫 InvokeVoidAsync ,且不會傳回值:

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

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

Pages/CallJsExample9.razor:

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

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

如需從 JavaScript 呼叫 .NET 時使用位元組陣列的資訊,請參閱在 ASP.NET Core Blazor 中從 JavaScript 函式呼叫 .NET 方法

JavaScript Interop 呼叫的大小限制

本節僅適用于 Blazor Server 應用程式。 在 Blazor WebAssembly 中,架構不會限制 JavaScript (JS) Interop 輸入和輸出的大小。

在 Blazor Server 中, JS Interop 呼叫的大小受限於中樞方法允許的傳入 SignalR 訊息大小上限,預設為 HubOptions.MaximumReceiveMessageSize (:32 KB) 。 JS至大於 MaximumReceiveMessageSize 擲回錯誤的 .NET SignalR 訊息。 架構不會限制從中樞到用戶端的訊息大小 SignalR 。

當記錄未設定為 [偵錯] 或 [追蹤] 時 SignalR ,訊息大小錯誤只會出現在瀏覽器的開發人員工具主控台中:

錯誤:連線中斷連線時發生錯誤「錯誤:伺服器在關閉時傳回錯誤:連線已關閉併發生錯誤。」。

伺服器端記錄設定為 [偵錯] 或 [追蹤] 時 SignalR,伺服器端記錄會顯示InvalidDataException 訊息大小錯誤的 。

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

錯誤:

System.IO.InvalidDataException:超過 32768B 的訊息大小上限。 訊息大小可以在 AddHubOptions 中設定。

藉由在 中 Program.cs 設定 MaximumReceiveMessageSize 來增加限制。 下列範例會將接收訊息大小上限設定為 64 KB (64 * 1024) :

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR增加傳入訊息大小限制的成本是需要更多伺服器資源,而且會公開伺服器以增加惡意使用者的風險。 此外,將大量內容讀入記憶體做為字串或位元組陣列,也會導致配置與垃圾收集行程搭配運作不佳,因而造成額外的效能負面影響。

開發在應用程式中傳輸大量資料 JSBlazorBlazor Server 的程式碼時,請考慮下列指引:

  • 利用原生串流 Interop 支援來傳輸大於 SignalR 傳入訊息大小限制的資料:
  • 一般秘訣:
    • 請勿在 和 C# 程式碼中 JS 配置大型物件。
    • 當進程完成或取消時,釋放已取用的記憶體。
    • 針對安全性用途強制執行下列額外需求:
      • 宣告可以傳遞的檔案或資料大小上限。
      • 宣告從用戶端到伺服器的最低上傳速率。
    • 伺服器收到資料之後,資料可以是:
      • 暫時儲存在記憶體緩衝區中,直到收集所有區段為止。
      • 立即取用。 例如,資料可以立即儲存在資料庫中,或寫入磁片,因為收到每個區段。

Unmarshalled JavaScript Interop

Blazor WebAssembly 當 .NET 物件序列化為 JavaScript () JS Interop 且下列任一項為 true 時,元件可能會遇到效能不佳的情況:

  • 大量 .NET 物件會快速序列化。 例如,當 Interop 呼叫是以移動輸入裝置為基礎進行時 JS ,效能不佳,例如旋轉滑鼠滾輪。
  • 大型 .NET 物件或許多 .NET 物件必須針對 Interop 序列化 JS 。 例如,當 Interop 呼叫需要序列化數十個檔案時 JS ,效能不佳可能會導致。

IJSUnmarshalledObjectReference 表示可以叫用函式的物件參考 JS ,而不需要序列化 .NET 資料的額外負荷。

在下例中︰

  • 包含字串和整數 的結構 會傳遞至 未序列化至 JS 。
  • JS 函式會處理資料,並將布林值或字串傳回給呼叫端。
  • JS字串無法直接轉換成 .NET string 物件。 函式 unmarshalledFunctionReturnString 會呼叫 BINDING.js_string_to_mono_string 來管理字串的 JS 轉換。

注意

下列範例不是此案例的典型使用案例,因為傳遞至 JS的結構不會造成元件效能不佳。 此範例只會使用小型物件來示範傳遞未序列化 .NET 資料的概念。

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

警告

在未來的 .NET 版本中,函 js_string_to_mono_string 式名稱、行為和存在可能會變更。 例如:

  • 函式可能會重新命名。
  • 函式本身可能會遭到移除,以利架構自動轉換字串。

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

IJSUnmarshalledObjectReference如果實例未在 C# 程式碼中處置,則可以在 中 JS 處置它。 下列 dispose 函式會在從 JS 呼叫 時處置物件參考:

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

    ...
  };
}

陣列類型可以使用 從 JS 物件轉換成 .NET 物件 js_typed_array_to_array ,但 JS 陣列必須是具型別陣列。 從 JS 的陣列可以在 C# 程式碼中讀取為 .NET 物件陣列, (object[]) 。

您可以轉換其他資料類型,例如字串陣列,但需要建立新的 Mono 陣列物件 (mono_obj_array_new) 並設定其值 (mono_obj_array_set) 。

警告

JS 架構所提供的 Blazor 函式,例如 js_typed_array_to_arraymono_obj_array_newmono_obj_array_set ,受限於名稱變更、行為變更,或在未來的 .NET 版本中移除。

從 .NET 串流至 JavaScript

Blazor 支援直接從 .NET 串流至 JavaScript 的資料。 資料流程是使用 建立的 DotNetStreamReference

DotNetStreamReference 表示 .NET 資料流程,並使用下列參數:

  • stream:傳送至 JavaScript 的資料流程。
  • leaveOpen:判斷資料流程是否在傳輸之後保持開啟。 如果未提供值, leaveOpen 則預設為 false

在 JavaScript 中,使用陣列緩衝區或可讀取的資料流程來接收資料:

  • ArrayBuffer使用 :

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • ReadableStream使用 :

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

在 C# 程式碼中:

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

在上述範例中:

  • {STREAM} 位符代表 Stream 傳送至 JavaScript 的 。
  • JS 是插入的 IJSRuntime 實例。

從 ASP.NET Core中的 JavaScript 函式呼叫 .NET 方法, Blazor 涵蓋從 JavaScript串流至 .NET 的反向作業。

Blazor ASP.NET Core檔案下載涵蓋如何在 中 Blazor 下載檔案。

攔截 JavaScript 例外狀況

若要攔截 JS 例外狀況,請將 JS Interop 包裝在 區塊中try-catch並攔截 JSException

在下列範例中,函 nonFunctionJS 式不存在。 找不到函式時, JSException 會截獲 ,並 Message 指出下列錯誤:

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

Pages/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 函式

JS使用AbortController搭配 CancellationTokenSource 元件中的 ,從 C# 程式碼中止長時間執行的 JavaScript 函式。

下列 JSHelpers 類別包含模擬長時間執行的函式, longRunningFn 可持續計算,直到 AbortController.signalAbortController.abort 指出已呼叫為止。 此 sleep 函式是為了示範目的,以模擬長時間執行的函式執行速度緩慢,而且不會出現在生產程式碼中。 當元件呼叫 stopFn 時,會 longRunningFn 透過 上的 AbortSignal.aborted 迴圈條件式檢查發出中止的 while 訊號。

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

下列 CallJsExample12 元件:

Pages/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!

其他資源