ASP.NET Core :::no-loc(Blazor)::: で .NET メソッドから JavaScript 関数を呼び出すCall JavaScript functions from .NET methods in ASP.NET Core :::no-loc(Blazor):::

作成者: Javier Calvarro NelsonDaniel RothLuke LathamBy Javier Calvarro Nelson, Daniel Roth, and Luke Latham

:::no-loc(Blazor)::: アプリでは、.NET メソッドから JavaScript 関数を呼び出すことも、JavaScript 関数から .NET メソッドを呼び出すこともできます。A :::no-loc(Blazor)::: app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions. これらのシナリオは、" JavaScript 相互運用 " (" JS 相互運用 ") と呼ばれます。These scenarios are called JavaScript interoperability ( JS interop ).

この記事では、.NET から JavaScript 関数を呼び出す方法について説明します。This article covers invoking JavaScript functions from .NET. JavaScript から .NET メソッドを呼び出す方法については、「ASP.NET Core :::no-loc(Blazor)::: で JavaScript 関数から .NET メソッドを呼び出す」を参照してください。For information on how to call .NET methods from JavaScript, see ASP.NET Core :::no-loc(Blazor)::: で JavaScript 関数から .NET メソッドを呼び出す.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

.NET から JavaScript を呼び出すには、IJSRuntime 抽象化を使用します。To call into JavaScript from .NET, use the IJSRuntime abstraction. JS 相互運用呼び出しを発行するには、コンポーネントに IJSRuntime 抽象化を挿入します。To issue JS interop calls, inject the IJSRuntime abstraction in your component. InvokeAsync は、JSON シリアル化可能な任意の数の引数と共に呼び出す JavaScript 関数の識別子を受け取ります。InvokeAsync takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments. 関数の識別子は、グローバル スコープ (window) に関連しています。The function identifier is relative to the global scope (window). window.someScope.someFunction を呼び出す場合、識別子は someScope.someFunction です。If you wish to call window.someScope.someFunction, the identifier is someScope.someFunction. 関数は、呼び出す前に登録する必要はありません。There's no need to register the function before it's called. また、戻り値の型 T も JSON シリアル化可能である必要があります。The return type T must also be JSON serializable. T は、返される JSON 型に最適にマップされる .NET 型と一致する必要があります。T should match the .NET type that best maps to the JSON type returned.

Promise を返す JavaScript 関数は、InvokeAsync と共に呼び出されます。JavaScript functions that return a Promise are called with InvokeAsync. InvokeAsync は Promise のラップを解除し、Promise によって待機された値を返します。InvokeAsync unwraps the Promise and returns the value awaited by the Promise.

:::no-loc(Blazor Server)::: アプリでプリレンダリングが有効になっている場合、最初のプリレンダリング中に JavaScript を呼び出すことはできません。For :::no-loc(Blazor Server)::: apps with prerendering enabled, calling into JavaScript isn't possible during the initial prerendering. JavaScript 相互運用呼び出しは、ブラウザーとの接続が確立されるまで遅延させる必要があります。JavaScript interop calls must be deferred until after the connection with the browser is established. 詳細については、「:::no-loc(Blazor Server)::: アプリがプリレンダリングされていることを検出する」セクションを参照してください。For more information, see the Detect when a :::no-loc(Blazor Server)::: app is prerendering section.

次の例は、JavaScript ベースのデコーダーである TextDecoder に基づいています。The following example is based on TextDecoder, a JavaScript-based decoder. この例では、開発者コードから既存の JavaScript API に要件をオフロードする C# メソッドから JavaScript 関数を呼び出す方法を示します。The example demonstrates how to invoke a JavaScript function from a C# method that offloads a requirement from developer code to an existing JavaScript API. JavaScript 関数は、C# メソッドからバイト配列を受け取り、配列をデコードし、テキストをコンポーネントに返して表示できるようにします。The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.

wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) または Pages/_Host.cshtml (:::no-loc(Blazor Server):::) の <head> 要素内で、TextDecoder を使用して、渡された配列をデコードし、デコードした値を返す JavaScript 関数を提供します。Inside the <head> element of wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) or Pages/_Host.cshtml (:::no-loc(Blazor Server):::), provide a JavaScript function that uses TextDecoder to decode a passed array and return the decoded value:

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

JavaScript コードでは、前の例で示したコードのように、スクリプト ファイルへの参照を使用して JavaScript ファイル (.js) から読み込むこともできます。JavaScript code, such as the code shown in the preceding example, can also be loaded from a JavaScript file (.js) with a reference to the script file:

<script src="exampleJsInterop.js"></script>

次のコンポーネント:The following component:

  • コンポーネント ボタン ( Convert Array ) が選択された場合、JSRuntime を使用して convertArray JavaScript 関数を呼び出します。Invokes the convertArray JavaScript function using JSRuntime when a component button ( Convert Array ) is selected.
  • JavaScript 関数が呼び出されると、渡された配列が文字列に変換されます。After the JavaScript function is called, the passed array is converted into a string. 文字列は、表示できるようにコンポーネントに返されます。The string is returned to the component for display.
@page "/call-js-example"
@inject IJSRuntime JSRuntime;

<h1>Call JavaScript Function Example</h1>

<button type="button" class="btn btn-primary" @onclick="ConvertArray">
    Convert Array
</button>

<p class="mt-2" style="font-size:1.6em">
    <span class="badge badge-success">
        @convertedText
    </span>
</p>

@code {
    // Quote (c)2005 Universal Pictures: Serenity
    // https://www.uphe.com/movies/serenity
    // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/

    private MarkupString convertedText =
        new MarkupString("Select the <b>Convert Array</b> button.");

    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()
    {
        var text =
            await JSRuntime.InvokeAsync<string>("convertArray", quoteArray);

        convertedText = new MarkupString(text);
    }
}

IJSRuntimeIJSRuntime

IJSRuntime 抽象化を使用するには、次のいずれかの方法を採用します。To use the IJSRuntime abstraction, adopt any of the following approaches:

  • :::no-loc(Razor)::: コンポーネント (.razor) に IJSRuntime 抽象化を挿入します。Inject the IJSRuntime abstraction into the :::no-loc(Razor)::: component (.razor):

    @inject IJSRuntime JSRuntime
    
    @code {
        protected override void OnInitialized()
        {
            StocksService.OnStockTickerUpdated += stockUpdate =>
            {
                JSRuntime.InvokeVoidAsync("handleTickerChanged",
                    stockUpdate.symbol, stockUpdate.price);
            };
        }
    }
    

    wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) または Pages/_Host.cshtml (:::no-loc(Blazor Server):::) の <head> 要素内で、handleTickerChanged JavaScript 関数を指定します。Inside the <head> element of wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) or Pages/_Host.cshtml (:::no-loc(Blazor Server):::), provide a handleTickerChanged JavaScript function. 関数は JSRuntimeExtensions.InvokeVoidAsync を指定して呼び出され、値を返しません。The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
      };
    </script>
    
  • IJSRuntime 抽象化をクラス (.cs) に挿入します。Inject the IJSRuntime abstraction into a class (.cs):

    public class JsInteropClasses
    {
        private readonly IJSRuntime jsRuntime;
    
        public JsInteropClasses(IJSRuntime jsRuntime)
        {
            this.jsRuntime = jsRuntime;
        }
    
        public ValueTask<string> TickerChanged(string data)
        {
            return jsRuntime.InvokeAsync<string>(
                "handleTickerChanged",
                stockUpdate.symbol,
                stockUpdate.price);
        }
    }
    

    wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) または Pages/_Host.cshtml (:::no-loc(Blazor Server):::) の <head> 要素内で、handleTickerChanged JavaScript 関数を指定します。Inside the <head> element of wwwroot/index.html (:::no-loc(Blazor WebAssembly):::) or Pages/_Host.cshtml (:::no-loc(Blazor Server):::), provide a handleTickerChanged JavaScript function. 関数は JSRuntime.InvokeAsync を指定して呼び出され、次の値を返します。The function is called with JSRuntime.InvokeAsync and returns a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
        return 'Done!';
      };
    </script>
    
  • BuildRenderTree でコンテンツを動的に生成するには、[Inject] 属性を使用します。For dynamic content generation with BuildRenderTree, use the [Inject] attribute:

    [Inject]
    IJSRuntime JSRuntime { get; set; }
    

このトピックに添付されているクライアント側のサンプル アプリでは、ユーザー入力を受け取り、ウェルカム メッセージを表示するために、DOM とやりとりする 2 つの JavaScript 関数をアプリで使用できます。In the client-side sample app that accompanies this topic, two JavaScript functions are available to the app that interact with the DOM to receive user input and display a welcome message:

  • showPrompt:ユーザー入力 (ユーザーの名前) を受け入れるプロンプトを生成し、名前を呼び出し元に返します。showPrompt: Produces a prompt to accept user input (the user's name) and returns the name to the caller.
  • displayWelcome:welcomeid を持つ DOM オブジェクトに、呼び出し元からのウェルカム メッセージを割り当てます。displayWelcome: Assigns a welcome message from the caller to a DOM object with an id of welcome.

wwwroot/exampleJsInterop.js:wwwroot/exampleJsInterop.js:

警告

ご覧になっていたサンプルが移動されたようです。 この問題の修正に取り組んでおりますので、ご安心ください。

JavaScript ファイルを参照する <script> タグを wwwroot/index.html ファイル (:::no-loc(Blazor WebAssembly):::) または Pages/_Host.cshtml ファイル (:::no-loc(Blazor Server):::) に配置します。Place the <script> tag that references the JavaScript file in the wwwroot/index.html file (:::no-loc(Blazor WebAssembly):::) or Pages/_Host.cshtml file (:::no-loc(Blazor Server):::).

wwwroot/index.html (:::no-loc(Blazor WebAssembly):::):wwwroot/index.html (:::no-loc(Blazor WebAssembly):::):

警告

ご覧になっていたサンプルが移動されたようです。 この問題の修正に取り組んでおりますので、ご安心ください。

Pages/_Host.cshtml (:::no-loc(Blazor Server):::):Pages/_Host.cshtml (:::no-loc(Blazor Server):::):

警告

ご覧になっていたサンプルが移動されたようです。 この問題の修正に取り組んでおりますので、ご安心ください。

<script> タグを動的に更新できないため、<script> タグをコンポーネント ファイル内に配置しないでください。Don't place a <script> tag in a component file because the <script> tag can't be updated dynamically.

.NET メソッドは、IJSRuntime.InvokeAsync を呼び出して、exampleJsInterop.js ファイル内の JavaScript 関数と相互運用します。.NET methods interop with the JavaScript functions in the exampleJsInterop.js file by calling IJSRuntime.InvokeAsync.

IJSRuntime 抽象化は、:::no-loc(Blazor Server)::: のシナリオを可能にするために非同期です。The IJSRuntime abstraction is asynchronous to allow for :::no-loc(Blazor Server)::: scenarios. アプリが :::no-loc(Blazor WebAssembly)::: アプリであり、JavaScript 関数を同期的に呼び出す必要がある場合は、IJSInProcessRuntime にダウンキャストし、代わりに Invoke を呼び出します。If the app is a :::no-loc(Blazor WebAssembly)::: app and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime and call Invoke instead. ほとんどの JS 相互運用ライブラリでは、確実にすべてのシナリオでライブラリを使用できるように、非同期 API を使用することをお勧めします。We recommend that most JS interop libraries use the async APIs to ensure that the libraries are available in all scenarios.

注意

標準 JavaScript モジュールで JavaScript の分離を有効にするには、「:::no-loc(Blazor)::: JavaScript の分離とオブジェクト参照」セクションを参照してください。To enable JavaScript isolation in standard JavaScript modules, see the :::no-loc(Blazor)::: JavaScript isolation and object references section.

サンプル アプリには、JS 相互運用を示すコンポーネントが含まれています。The sample app includes a component to demonstrate JS interop. コンポーネント:The component:

  • JavaScript プロンプトを介してユーザー入力を受け取ります。Receives user input via a JavaScript prompt.
  • テキストをコンポーネントに返して処理します。Returns the text to the component for processing.
  • DOM とやりとりしてウェルカム メッセージを表示する 2 番目の JavaScript 関数を呼び出します。Calls a second JavaScript function that interacts with the DOM to display a welcome message.

Pages/JsInterop.razor:Pages/JsInterop.razor:

@page "/JSInterop"
@using {APP ASSEMBLY}.JsInteropClasses
@inject IJSRuntime JSRuntime

<h1>JavaScript Interop</h1>

<h2>Invoke JavaScript functions from .NET methods</h2>

<button type="button" class="btn btn-primary" @onclick="TriggerJsPrompt">
    Trigger JavaScript Prompt
</button>

<h3 id="welcome" style="color:green;font-style:italic"></h3>

@code {
    public async Task TriggerJsPrompt()
    {
        var name = await JSRuntime.InvokeAsync<string>(
                "exampleJsFunctions.showPrompt",
                "What's your name?");

        await JSRuntime.InvokeVoidAsync(
                "exampleJsFunctions.displayWelcome",
                $"Hello {name}! Welcome to :::no-loc(Blazor):::!");
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアプリ アセンブリ名です (例: :::no-loc(Blazor):::Sample)。The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, :::no-loc(Blazor):::Sample).

  1. コンポーネントの Trigger JavaScript Prompt ボタンを選択して TriggerJsPrompt を実行すると、wwwroot/exampleJsInterop.js ファイル内に指定した JavaScript showPrompt 関数が呼び出されます。When TriggerJsPrompt is executed by selecting the component's Trigger JavaScript Prompt button, the JavaScript showPrompt function provided in the wwwroot/exampleJsInterop.js file is called.
  2. showPrompt 関数は、ユーザー入力 (ユーザーの名前) を受け取ります。これは、HTML エンコードされ、コンポーネントに返されます。The showPrompt function accepts user input (the user's name), which is HTML-encoded and returned to the component. コンポーネントにより、ユーザーの名前がローカル変数 name に格納されます。The component stores the user's name in a local variable, name.
  3. name に格納された文字列は、ウェルカム メッセージに組み込まれます。このメッセージが JavaScript 関数 displayWelcome に渡され、ウェルカム メッセージが見出しタグにレンダリングされます。The string stored in name is incorporated into a welcome message, which is passed to a JavaScript function, displayWelcome, which renders the welcome message into a heading tag.

void JavaScript 関数を呼び出すCall a void JavaScript function

次の場合に JSRuntimeExtensions.InvokeVoidAsync を使用します。Use JSRuntimeExtensions.InvokeVoidAsync for the following:

  • void(0)/void 0 または undefined を返す JavaScript 関数。JavaScript functions that return void(0)/void 0 or undefined.
  • JavaScript 呼び出しの結果を読み取るために .NET が不要な場合。If .NET isn't required to read the result of a JavaScript call.

:::no-loc(Blazor Server)::: アプリがプリレンダリングされていることを検出するDetect when a :::no-loc(Blazor Server)::: app is prerendering

Blazor サーバー アプリをプリレンダリングしている間、ブラウザーとの接続が確立されていないため、JavaScript への呼び出しなどの特定のアクションは実行できません。While a Blazor Server app is prerendering, certain actions, such as calling into JavaScript, aren't possible because a connection with the browser hasn't been established. コンポーネントは、プリレンダリング時に異なるレンダリングが必要になる場合があります。Components may need to render differently when prerendered.

ブラウザーとの接続が確立されるまで JavaScript の相互運用呼び出しを遅らせるために、OnAfterRenderAsync コンポーネント ライフサイクル イベントを使用できます。To delay JavaScript interop calls until after the connection with the browser is established, you can use the OnAfterRenderAsync component lifecycle event. このイベントは、アプリが完全にレンダリングされ、クライアント接続が確立された後にのみ呼び出されます。This event is only called after the app is fully rendered and the client connection is established.

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

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

@code {
    private ElementReference divElement;

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

前のコード例では、wwwroot/index.html (Blazor WebAssembly) または Pages/_Host.cshtml (Blazor Server) の <head> 要素内で、setElementText JavaScript 関数を提供します。For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). 関数は JSRuntimeExtensions.InvokeVoidAsync を指定して呼び出され、値を返しません。The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

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

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. ほとんどのシナリオにおいて、JavaScript で DOM を直接変更することはお勧めできません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

次のコンポーネントは、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JavaScript の相互運用を使用する方法を示しています。The following component demonstrates how to use JavaScript interop as part of a component's initialization logic in a way that's compatible with prerendering. コンポーネントには、OnAfterRenderAsync 内からレンダリングの更新をトリガーできることが示されています。The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. このシナリオでは、開発者が無限ループを作成しないようにする必要があります。The developer must avoid creating an infinite loop in this scenario.

JSRuntime.InvokeAsync が呼び出されるとき、ElementRef は、以前のライフサイクル メソッドではなく OnAfterRenderAsync でのみ使用されます。コンポーネントがレンダリングされるまで JavaScript 要素が存在しないためです。Where JSRuntime.InvokeAsync is called, ElementRef is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JavaScript element until after the component is rendered.

StateHasChanged は、JavaScript の相互運用呼び出しから取得された新しい状態でコンポーネントを再度レンダリングするために呼び出されます。StateHasChanged is called to rerender the component with the new state obtained from the JavaScript interop call. StateHasChangedinfoFromJsnull である場合にのみ呼び出されるため、このコードで無限ループが作成されることはありません。The code doesn't create an infinite loop because StateHasChanged is only called when infoFromJs is null.

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

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

Set value via JS interop call:
<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string infoFromJs;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

前のコード例では、wwwroot/index.html (Blazor WebAssembly) または Pages/_Host.cshtml (Blazor Server) の <head> 要素内で、setElementText JavaScript 関数を提供します。For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). 関数は IJSRuntime.InvokeAsync を指定して呼び出され、次の値を返します。The function is called withIJSRuntime.InvokeAsync and returns a value:

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

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. ほとんどのシナリオにおいて、JavaScript で DOM を直接変更することはお勧めできません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

要素への参照をキャプチャするCapture references to elements

一部の JS 相互運用シナリオでは、HTML 要素への参照が必要です。Some JS interop scenarios require references to HTML elements. たとえば、UI ライブラリで初期化のための要素参照が必要な場合、focusplay などの要素でコマンドのような API の呼び出し必要になる可能性があります。For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus or play.

次の方法を使用して、コンポーネント内の HTML 要素への参照をキャプチャします。Capture references to HTML elements in a component using the following approach:

  • @ref 属性を HTML 要素に追加します。Add an @ref attribute to the HTML element.
  • 名前が @ref 属性の値に一致する ElementReference 型のフィールドを定義します。Define a field of type ElementReference whose name matches the value of the @ref attribute.

次の例は、username <input> 要素への参照をキャプチャする方法を示しています。The following example shows capturing a reference to the username <input> element:

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

@code {
    ElementReference username;
}

警告

:::no-loc(Blazor)::: とやりとりしない空の要素のコンテンツを変化させるには、要素参照のみを使用します。Only use an element reference to mutate the contents of an empty element that doesn't interact with :::no-loc(Blazor):::. このシナリオは、サードパーティの API から要素にコンテンツが提供される場合に便利です。This scenario is useful when a third-party API supplies content to the element. :::no-loc(Blazor)::: は要素とやりとりしないため、:::no-loc(Blazor)::: の要素表現と DOM との間に競合が発生する可能性がありません。Because :::no-loc(Blazor)::: doesn't interact with the element, there's no possibility of a conflict between :::no-loc(Blazor):::'s representation of the element and the DOM.

次の例では、:::no-loc(Blazor)::: が DOM とやりとりしてこの要素のリスト項目 (<li>) を設定するため、順序なしリスト (ul) のコンテンツを変化させるのは " 危険 " です。In the following example, it's dangerous to mutate the contents of the unordered list (ul) because :::no-loc(Blazor)::: interacts with the DOM to populate this element's list items (<li>):

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

JS 相互運用により要素 MyList のコンテンツが変更され、:::no-loc(Blazor)::: でその要素に差分を適用しようとした場合、差分は DOM と一致しません。If JS interop mutates the contents of element MyList and :::no-loc(Blazor)::: attempts to apply diffs to the element, the diffs won't match the DOM.

.NET コードに関しては、ElementReference は不透明なハンドルです。As far as .NET code is concerned, an ElementReference is an opaque handle. ElementReference を使用して実行できるのは、JS 相互運用を介して JavaScript コードに渡すこと " のみ " です。The only thing you can do with ElementReference is pass it through to JavaScript code via JS interop. これを行うと、JavaScript 側のコードが HTMLElement インスタンスを受け取り、通常の DOM API で使用できます。When you do so, the JavaScript-side code receives an HTMLElement instance, which it can use with normal DOM APIs.

たとえば、次のコードでは、要素にフォーカスを設定できるようにする .NET 拡張メソッドを定義しています。For example, the following code defines a .NET extension method that enables setting the focus on an element:

exampleJsInterop.js:exampleJsInterop.js:

window.exampleJsFunctions = {
  focusElement : function (element) {
    element.focus();
  }
}

値を返さない JavaScript 関数を呼び出すには、JSRuntimeExtensions.InvokeVoidAsync を使用します。To call a JavaScript function that doesn't return a value, use JSRuntimeExtensions.InvokeVoidAsync. 次のコードは、キャプチャされた ElementReference で前述の JavaScript 関数を呼び出して、ユーザー名入力にフォーカスを設定します。The following code sets the focus on the username input by calling the preceding JavaScript function with the captured ElementReference:

@inject IJSRuntime JSRuntime

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

<button @onclick="TriggerClick">
    Trigger button click on exampleButton
</button>

@code {
    private ElementReference exampleButton;

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

拡張メソッドを使用するには、IJSRuntime インスタンスを受け取る静的拡張メソッドを作成します。To use an extension method, create a static extension method that receives the IJSRuntime instance:

public static async Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    await jsRuntime.InvokeVoidAsync(
        "exampleJsFunctions.focusElement", elementRef);
}

Focus メソッドは、オブジェクトで直接呼び出されます。The Focus method is called directly on the object. 次の例では、Focus メソッドが JsInteropClasses 名前空間から使用できることを前提としています。The following example assumes that the Focus method is available from the JsInteropClasses namespace:

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<button @ref="exampleButton" />

<button @onclick="TriggerClick">
    Trigger button click on exampleButton
</button>

@code {
    private ElementReference exampleButton;

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

重要

username 変数は、コンポーネントがレンダリングされた後にのみ設定されます。The username variable is only populated after the component is rendered. 未入力の ElementReference が JavaScript コードに渡された場合、JavaScript コードは null の値を受け取ります。If an unpopulated ElementReference is passed to JavaScript code, the JavaScript code receives a value of null. コンポーネントのレンダリングが完了した後に要素参照を操作する (要素に初期フォーカスを設定する) には、OnAfterRenderAsync または OnAfterRender コンポーネント ライフサイクル メソッドを使用します。To manipulate element references after the component has finished rendering (to set the initial focus on an element) use the OnAfterRenderAsync or OnAfterRender component lifecycle methods.

ジェネリック型を操作して値を返す場合は、ValueTask<TResult> を使用します。When working with generic types and returning a value, use ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime jsRuntime)
{
    return jsRuntime.InvokeAsync<T>(
        "exampleJsFunctions.doSomethingGeneric", elementRef);
}

GenericMethod は、型を持つオブジェクトで直接呼び出されます。GenericMethod is called directly on the object with a type. 次の例では、GenericMethodJsInteropClasses 名前空間から使用できることを前提としています。The following example assumes that the GenericMethod is available from the JsInteropClasses namespace:

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

コンポーネント間で要素を参照するReference elements across components

ElementReference インスタンスは、コンポーネントの OnAfterRender メソッドでのみ有効であることが保証されます (要素参照は struct です)。そのため、コンポーネント間で要素参照を渡すことはできません。An ElementReference instance is only guaranteed valid in a component's OnAfterRender method (and an element reference is a struct), so an element reference can't be passed between components. 親コンポーネントが要素参照を他のコンポーネントで使用できるようにするために、親コンポーネントは次のことを実行できます。For a parent component to make an element reference available to other components, the parent component can:

  • 子コンポーネントがコールバックを登録できるようにします。Allow child components to register callbacks.
  • 渡された要素参照を使用して、登録されたコールバックをOnAfterRender イベント中に呼び出します。Invoke the registered callbacks during the OnAfterRender event with the passed element reference. 間接的には、この方法により、子コンポーネントが親の要素参照とやりとりできるようになります。Indirectly, this approach allows child components to interact with the parent's element reference.

次の :::no-loc(Blazor WebAssembly)::: の例でこのアプローチを示します。The following :::no-loc(Blazor WebAssembly)::: example illustrates the approach.

wwwroot/index.html<head> では、次のことが行われます。In the <head> of wwwroot/index.html:

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

wwwroot/index.html<body> では、次のことが行われます。In the <body> of wwwroot/index.html:

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

Pages/Index.razor (親コンポーネント):Pages/Index.razor (parent component):

@page "/"

<h1 @ref="title">Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is :::no-loc(Blazor)::: working for you?" />

Pages/Index.razor.cs:Pages/Index.razor.cs:

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

namespace {APP ASSEMBLY}.Pages
{
    public partial class Index : 
        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, Index self)
            {
                Observer = observer;
                Self = self;
            }

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

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

プレースホルダー {APP ASSEMBLY} は、アプリのアプリ アセンブリ名です (例: :::no-loc(Blazor):::Sample)。The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, :::no-loc(Blazor):::Sample).

Shared/SurveyPrompt.razor (子コンポーネント):Shared/SurveyPrompt.razor (child component):

@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:Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace {APP ASSEMBLY}.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();

            if (subscription != null)
            {
                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();
        }
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアプリ アセンブリ名です (例: :::no-loc(Blazor):::Sample)。The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, :::no-loc(Blazor):::Sample).

JS 相互運用呼び出しの強化Harden JS interop calls

JS 相互運用は、ネットワーク エラーにより失敗する可能性があるため、信頼性の低いものとして扱う必要があります。JS interop may fail due to networking errors and should be treated as unreliable. 既定では、:::no-loc(Blazor Server)::: アプリでは、サーバー上の JS 相互運用の呼び出しは 1 分後にタイムアウトします。By default, a :::no-loc(Blazor Server)::: app times out JS interop calls on the server after one minute. アプリでより積極的なタイムアウトが許容される場合は、次のいずれかの方法を使用してタイムアウトを設定します。If an app can tolerate a more aggressive timeout, set the timeout using one of the following approaches:

  • Startup.ConfigureServices でグローバルに、タイムアウトを指定します。Globally in Startup.ConfigureServices, specify the timeout:

    services.AddServerSide:::no-loc(Blazor):::(
        options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
    
  • コンポーネント コードでの呼び出しごとに、1 回の呼び出しでタイムアウトを指定できます。Per-invocation in component code, a single call can specify the timeout:

    var result = await JSRuntime.InvokeAsync<string>("MyJSOperation", 
        TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });
    

リソース枯渇の詳細については、「ASP.NET Core Blazor Server の脅威の緩和のガイダンス」を参照してください。For more information on resource exhaustion, see ASP.NET Core Blazor Server の脅威の緩和のガイダンス.

クラス ライブラリで相互運用コードを共有するShare interop code in a class library

JS 相互運用コードは、クラス ライブラリに含めて NuGet パッケージ内でコードを共有することができます。JS interop code can be included in a class library, which allows you to share the code in a NuGet package.

クラス ライブラリは、ビルドされたアセンブリでの JavaScript リソースの埋め込みを処理します。The class library handles embedding JavaScript resources in the built assembly. JavaScript ファイルは、wwwroot フォルダーに配置されます。The JavaScript files are placed in the wwwroot folder. ライブラリのビルド時にツールがリソースの埋め込みを行います。The tooling takes care of embedding the resources when the library is built.

ビルド済みの NuGet パッケージは、NuGet パッケージの参照と同じ方法で、アプリのプロジェクト ファイルで参照されます。The built NuGet package is referenced in the app's project file the same way that any NuGet package is referenced. パッケージが復元されたら、アプリ コードを C# と同じように JavaScript に呼び出すことができます。After the package is restored, app code can call into JavaScript as if it were C#.

詳細については、「ASP.NET Core Razor コンポーネント クラス ライブラリ」を参照してください。For more information, see ASP.NET Core Razor コンポーネント クラス ライブラリ.

循環オブジェクト参照の回避Avoid circular object references

循環参照を含むオブジェクトは、次のいずれに対しても、クライアントでシリアル化することはできません。Objects that contain circular references can't be serialized on the client for either:

  • .NET メソッドの呼び出し。.NET method calls.
  • 戻り値の型に循環参照がある場合の、C# からの JavaScript メソッドの呼び出し。JavaScript method calls from C# when the return type has circular references.

詳細については、次のイシューを参照してください。For more information, see the following issues:

:::no-loc(Blazor)::: JavaScript の分離とオブジェクト参照:::no-loc(Blazor)::: JavaScript isolation and object references

:::no-loc(Blazor)::: により、標準 JavaScript モジュールで JavaScript の分離が有効にされます。:::no-loc(Blazor)::: enables JavaScript isolation in standard JavaScript modules. JavaScript の分離には、次のような利点があります。JavaScript isolation provides the following benefits:

  • インポートされる JavaScript によって、グローバル名前空間が汚染されなくなります。Imported JavaScript no longer pollutes the global namespace.
  • ライブラリおよびコンポーネントのコンシューマーは、関連する JavaScript をインポートする必要がありません。Consumers of a library and components aren't required to import the related JavaScript.

たとえば、次の JavaScript モジュールにより、ブラウザー プロンプトを表示する JavaScript 関数がエクスポートされます。For example, the following JavaScript module exports a JavaScript function for showing a browser prompt:

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

前の JavaScript モジュールを静的 Web アセット (wwwroot/exampleJsInterop.js) として .NET ライブラリに追加し、IJSRuntime サービスを使用してそのモジュールを .NET コードにインポートします。Add the preceding JavaScript module to a .NET library as a static web asset (wwwroot/exampleJsInterop.js) and then import the module into the .NET code using the IJSRuntime service. サービスは、次の例では jsRuntime (表示はなし) として挿入されます。The service is injected as jsRuntime (not shown) for the following example:

var module = await jsRuntime.InvokeAsync<IJSObjectReference>(
    "import", "./_content/MyComponents/exampleJsInterop.js");

前の例の import 識別子は、JavaScript モジュールをインポートするために特別に使用される特殊な識別子です。The import identifier in the preceding example is a special identifier used specifically for importing a JavaScript module. 安定した静的な Web アセット パスを使用してモジュールを指定します: _content/{LIBRARY NAME}/{PATH UNDER WWWROOT}Specify the module using its stable static web asset path: _content/{LIBRARY NAME}/{PATH UNDER WWWROOT}. プレースホルダー {LIBRARY NAME} は、ライブラリの名前です。The placeholder {LIBRARY NAME} is the library name. プレースホルダー {PATH UNDER WWWROOT} は、wwwroot の下にあるスクリプトへのパスです。The placeholder {PATH UNDER WWWROOT} is the path to the script under wwwroot.

IJSRuntime により、モジュールが IJSObjectReference としてインポートされます。これは、.NET コードから JavaScript オブジェクトへの参照を表します。IJSRuntime imports the module as a IJSObjectReference, which represents a reference to a JavaScript object from .NET code. モジュールからエクスポートされた JavaScript 関数を呼び出すには、IJSObjectReference を使用します。Use the IJSObjectReference to invoke exported JavaScript functions from the module:

public async ValueTask<string> Prompt(string message)
{
    return await module.InvokeAsync<string>("showPrompt", message);
}

IJSInProcessObjectReference は、関数を同期的に呼び出すことができる JavaScript オブジェクトへの参照を表します。IJSInProcessObjectReference represents a reference to a JavaScript object whose functions can be invoked synchronously.

IJSUnmarshalledObjectReference は、.NET データをシリアル化するオーバーヘッドなしで関数を呼び出すことができる JavaScript オブジェクトへの参照を表します。IJSUnmarshalledObjectReference represents a reference to an JavaScript object whose functions can be invoked without the overhead of serializing .NET data. これは、パフォーマンスが重要な場合に :::no-loc(Blazor WebAssembly)::: で使用できます。This can be used in :::no-loc(Blazor WebAssembly)::: when performance is crucial:

window.unmarshalledInstance = {
  helloWorld: function (personNamePointer) {
    const personName = :::no-loc(Blazor):::.platform.readStringField(value, 0);
    return `Hello ${personName}`;
  }
};
var unmarshalledRuntime = (IJSUnmarshalledRuntime)jsRuntime;
var jsUnmarshalledReference = unmarshalledRuntime
    .InvokeUnmarshalled<IJSUnmarshalledObjectReference>("unmarshalledInstance");

string helloWorldString = jsUnmarshalledReference.InvokeUnmarshalled<string, string>(
    "helloWorld");

UI をレンダリングする JavaScript ライブラリの使用 (DOM 要素)Use of JavaScript libraries that render UI (DOM elements)

ブラウザー DOM 内に表示可能なユーザー インターフェイス要素を生成する JavaScript ライブラリを使用することが必要になる場合があります。Sometimes you may wish to use JavaScript libraries that produce visible user interface elements within the browser DOM. :::no-loc(Blazor)::: の差分システムは、DOM 要素のツリーに対する制御に依存しており、外部コードによって DOM ツリーが変更されて、差分を適用するためのメカニズムが無効になるとエラーが発生するため、一見すると、これは困難に思えるかもしれません。At first glance, this might seem difficult because :::no-loc(Blazor):::'s diffing system relies on having control over the tree of DOM elements and runs into errors if some external code mutates the DOM tree and invalidates its mechanism for applying diffs. これは、:::no-loc(Blazor)::: に固有の制限ではありません。This isn't a :::no-loc(Blazor):::-specific limitation. 差分ベースの UI フレームワークでは同じ課題が発生します。The same challenge occurs with any diff-based UI framework.

幸い、外部で生成された UI を :::no-loc(Blazor)::: コンポーネントの UI に確実に埋め込むのは簡単です。Fortunately, it's straightforward to embed externally-generated UI within a :::no-loc(Blazor)::: component UI reliably. 推奨される方法は、コンポーネントのコード (.razor ファイル) で空の要素を生成することです。The recommended technique is to have the component's code (.razor file) produce an empty element. :::no-loc(Blazor)::: の差分システムに関する限り、要素は常に空であるため、レンダラーによって要素は再帰されず、代わりにその内容はそのままの状態になります。As far as :::no-loc(Blazor):::'s diffing system is concerned, the element is always empty, so the renderer does not recurse into the element and instead leaves its contents alone. これにより、外部で管理されている任意の内容を要素に設定しても安全になります。This makes it safe to populate the element with arbitrary externally-managed content.

次の例はこの概念を示したものです。The following example demonstrates the concept. if ステートメント内で、firstRendertrue の場合は、myElement に対する何らかの処理を行います。Within the if statement when firstRender is true, do something with myElement. たとえば、外部の JavaScript ライブラリを呼び出してそれを設定します。For example, call an external JavaScript library to populate it. このコンポーネント自体が削除されるまで、要素の内容が :::no-loc(Blazor)::: によって操作されることはありません。:::no-loc(Blazor)::: leaves the element's contents alone until this component itself is removed. コンポーネントが削除されると、コンポーネントの DOM サブツリー全体も削除されます。When the component is removed, the component's entire DOM subtree is also removed.

<h1>Hello! This is a :::no-loc(Blazor)::: component rendered at @DateTime.Now</h1>

<div @ref="myElement"></div>

@code {
    HtmlElement myElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

さらに詳細な例として、オープンソースの Mapbox API を使用して対話型マップをレンダリングする次のようなコンポーネントについて考えてみます。As a more detailed example, consider the following component that renders an interactive map using the open-source Mapbox APIs:

@inject IJSRuntime JS
@implements IAsyncDisposable

<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
{
    ElementReference mapElement;
    IJSObjectReference mapModule;
    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);
        }
    }

    Task ShowAsync(double latitude, double longitude)
        => mapModule.InvokeVoidAsync("setMapCenter", mapInstance, latitude, 
            longitude).AsTask();

    private async ValueTask IAsyncDisposable.DisposeAsync()
    {
        await mapInstance.DisposeAsync();
        await mapModule.DisposeAsync();
    }
}

wwwroot/mapComponent.js に配置する必要のある、対応する JavaScript モジュールは、次のとおりです。The corresponding JavaScript module, which should be placed at wwwroot/mapComponent.js, is as follows:

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

// TO MAKE THE MAP APPEAR YOU MUST ADD YOUR ACCESS TOKEN FROM 
// https://account.mapbox.com
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]);
}

前の例で、文字列 {ACCESS TOKEN} は、 https://account.mapbox.com から取得できる有効なアクセス トークンに置き換えます。In the preceding example, replace the string {ACCESS TOKEN} with a valid access token that you can get from https://account.mapbox.com.

正しいスタイルを生成するには、ホストの HTML ページ (index.html または _Host.cshtml) に次のスタイル シート タグを追加します。To produce correct styling, add the following stylesheet tag to the host HTML page (index.html or _Host.cshtml):

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

前の例で生成される対話型のマップ UI で、ユーザーは次のことができます。The preceding example produces an interactive map UI, in which the user:

  • ドラッグしてスクロールまたはズームできます。Can drag to scroll or zoom.
  • ボタンをクリックして、あらかじめ定義されている場所に移動します。Click buttons to jump to predefined locations.

Mapbox による東京の市街地図。英国のブリストルと日本の東京を選択するためのボタンがあります

理解しておくべき重要な点は次のとおりです。The key points to understand are:

  • @ref="mapElement" が含まれる <div> は、:::no-loc(Blazor)::: に関する限り空のままになります。The <div> with @ref="mapElement" is left empty as far as :::no-loc(Blazor)::: is concerned. したがって、やがて mapbox-gl.js によって設定されたり、内容が変更されたりしても安全です。It's therefore safe for mapbox-gl.js to populate it and modify its contents over time. この手法は、UI をレンダリングする任意の JavaScript ライブラリで使用できます。You can use this technique with any JavaScript library that renders UI. ページの他の部分に手を伸ばして変更しようとしない限り、サードパーティの JavaScript SPA フレームワークのコンポーネントを :::no-loc(Blazor)::: コンポーネントの内部に埋め込むことさえできます。You could even embed components from a third-party JavaScript SPA framework inside :::no-loc(Blazor)::: components, as long as they don't try to reach out and modify other parts of the page. :::no-loc(Blazor)::: によって空と見なされない要素を、外部の JavaScript コードで変更することは、安全では " ありません "。It is not safe for external JavaScript code to modify elements that :::no-loc(Blazor)::: does not regard as empty.
  • このアプローチを使用する場合は、:::no-loc(Blazor)::: によって DOM 要素が保持または破棄される方法に関する規則に留意してください。When using this approach, bear in mind the rules about how :::no-loc(Blazor)::: retains or destroys DOM elements. 前の例で、既定では DOM 要素が可能な限り保持されるため、コンポーネントにより安全にボタン クリック イベントが処理され、既存のマップ インスタンスが更新されます。In the preceding example, the component safely handles button click events and updates the existing map instance because DOM elements are retained where possible by default. @foreach ループの内側からマップ要素のリストをレンダリングしていた場合は、@key を使用して、コンポーネントのインスタンスを確実に保持する必要があります。If you were rendering a list of map elements from inside a @foreach loop, you want to use @key to ensure the preservation of component instances. そうしないと、リスト データを変更した場合、コンポーネントのインスタンスによって前のインスタンスの状態が望ましくない状態で保持される可能性があります。Otherwise, changes in the list data could cause component instances to retain the state of previous instances in an undesirable manner. 詳細については、@key を使用した要素とコンポーネントの保持に関する記事を参照してください。For more information, see using @key to preserve elements and components.

また、前の例では、JavaScript のロジックと依存関係を ES6 モジュール内にカプセル化し、import 識別子を使用して動的に読み込むことができる方法が示されています。Additionally, the preceding example shows how it's possible to encapsulate JavaScript logic and dependencies within an ES6 module and load it dynamically using the import identifier. 詳細については、JavaScript の分離とオブジェクト参照に関する記事を参照してください。For more information, see JavaScript isolation and object references.

JS 相互運用呼び出しのサイズ制限Size limits on JS interop calls

:::no-loc(Blazor WebAssembly)::: では、フレームワークによって JS 相互運用呼び出しの入力と出力のサイズが制限されることはありません。In :::no-loc(Blazor WebAssembly):::, the framework doesn't impose limits on the size of inputs and outputs of JS interop calls.

:::no-loc(Blazor Server)::: では、JS 相互運用呼び出しの結果が、:::no-loc(SignalR)::: (<xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize>) によって適用されるペイロードの最大サイズで制限されます。既定値は 32 KB です。In :::no-loc(Blazor Server):::, the result of a JS interop call is limited by the maximum payload size enforced by :::no-loc(SignalR)::: (<xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize>), which defaults to 32 KB. アプリケーションで <xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize> よりもペイロードが大きい JS 相互運用呼び出しに応答しようとすると、エラーがスローされます。Applications that attempt to respond to a JS interop call with a payload larger than <xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize> throw an error. <xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize> を変更することで、制限をより大きく構成できます。A larger limit can be configured by modifying <xref:Microsoft.AspNetCore.:::no-loc(SignalR):::.HubOptions.MaximumReceiveMessageSize>. 次の例では、受信メッセージの最大サイズを 64 KB (64 * 1024 * 1024) に設定します。The following example sets the maximum receive message size to 64 KB (64 * 1024 * 1024):

services.AddServerSide:::no-loc(Blazor):::()
   .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024 * 1024);

:::no-loc(SignalR)::: の制限を増やすと、より多くのサーバー リソースを使用する必要が発生し、悪意のあるユーザーからのより大きなリスクにサーバーがさらされます。Increasing the :::no-loc(SignalR)::: limit comes at the cost of requiring the use of more server resources, and it exposes the server to increased risks from a malicious user. また、大量のコンテンツを文字列またはバイト配列としてメモリに読み取ると、ガベージ コレクターがうまく機能しない割り当てが発生する可能性もあり、その結果、パフォーマンスがさらに低下します。Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties. 大きなペイロードを読み取るための 1 つの選択肢は、小さいチャンクでコンテンツを送信し、ペイロードを Stream として処理することを検討することです。One option for reading large payloads is to consider sending the content in smaller chunks and processing the payload as a Stream. これは、大量の JSON ペイロードを読み取る場合、またはデータを JavaScript で生バイトとして利用できる場合に使用できます。This can be used when reading large JSON payloads or if data is available in JavaScript as raw bytes. :::no-loc(Blazor Server)::: で大規模なバイナリ ペイロードを送信する、InputFile コンポーネントに似た手法を使用する方法の例については、Binary Submit サンプル アプリを参照してください。For an example that demonstrates sending large binary payloads in :::no-loc(Blazor Server)::: that uses techniques similar to the InputFile component, see the Binary Submit sample app.

JavaScript と :::no-loc(Blazor)::: の間で大量のデータを転送するコードを開発するときは、次のガイダンスを考慮してください。Consider the following guidance when developing code that transfers a large amount of data between JavaScript and :::no-loc(Blazor)::::

  • データをより小さな部分にスライスし、すべてのデータがサーバーによって受信されるまでデータ セグメントを順番に送信します。Slice the data into smaller pieces, and send the data segments sequentially until all of the data is received by the server.
  • JavaScript および C# コードで大きなオブジェクトを割り当てないでください。Don't allocate large objects in JavaScript and C# code.
  • データを送受信するときに、メイン UI スレッドを長時間ブロックしないでください。Don't block the main UI thread for long periods when sending or receiving data.
  • プロセスの完了時またはキャンセル時に、消費していたメモリを解放します。Free any memory consumed when the process is completed or cancelled.
  • セキュリティ上の理由から、次の追加要件を適用します。Enforce the following additional requirements for security purposes:
    • 渡すことのできるファイルまたはデータの最大サイズを宣言します。Declare the maximum file or data size that can be passed.
    • クライアントからサーバーへの最小アップロード レートを宣言します。Declare the minimum upload rate from the client to the server.
  • データがサーバーによって受信されたら、データは:After the data is received by the server, the data can be:
    • すべてのセグメントが収集されるまで、一時的にメモリ バッファーに格納できます。Temporarily stored in a memory buffer until all of the segments are collected.
    • 直ちに消費できます。Consumed immediately. たとえば、データは、データベースに直ちに格納することも、セグメントを受信するたびにディスクに書き込むこともできます。For example, the data can be stored immediately in a database or written to disk as each segment is received.

その他の技術情報Additional resources