ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

Blazor アプリでは、.NET メソッドから JavaScript (JS) 関数を呼び出すことも、JS 関数から .NET メソッドを呼び出すこともできます。 これらのシナリオは、"JavaScript 相互運用性" ("JS 相互運用") と呼ばれます。

JS 相互運用の詳細なガイダンスについては、後の記事で説明されています。

Note

JavaScript の [JSImport]/[JSExport] 相互運用 API は、.NET 7 以降の ASP.NET Core のクライアント側コンポーネントで使用できます。

詳細については、「ASP.NET Core Blazor を使用した JavaScript Import/Export 相互運用」をご覧ください。

信頼されていないデータを含む対話型サーバー コンポーネントの圧縮

既定で有効になっている圧縮を使って、信頼されていないソースからのデータをレンダリングする、セキュリティで保護された (認証済み/承認済み) 対話型サーバー側コンポーネントは作成しないでください。 信頼されていないソースには、ルート パラメータ、クエリ文字列、JS 相互運用からのデータ、サードパーティのユーザーがコントロールできる他のデータ ソース (データベース、外部サービス) が含まれます。 詳細については、「ASP.NET Core BlazorSignalR ガイダンス」と「対話型サーバー側の ASP.NET Core Blazor レンダリングの脅威軽減策に関するガイダンス」を参照してください。

JavaScript 相互運用抽象化および機能パッケージ

@microsoft/dotnet-js-interop パッケージ (npmjs.com) (Microsoft.JSInterop NuGet パッケージ) は、.NET コードと JavaScript (JS) コード間の相互運用のための抽象化と機能を提供します。 参照ソースは dotnet/aspnetcoreGitHub リポジトリ (/src/JSInterop フォルダー) で入手できます。 詳細については、GitHub リポジトリのREADME.md ファイルを参照してください。

Note

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

TypeScript で JS 相互運用スクリプトを作成するための追加リソース:

DOM の操作

オブジェクトが Blazor と対話しない場合にのみ、JavaScript (JS) を使用して DOM を変換します。 Blazor は、DOM の表現を維持し、DOM オブジェクトと直接対話します。 Blazor によってレンダリングされた要素が JS を直接使用して、または JS 相互運用機能を使用して外部で変更された場合、DOM は Blazor の内部表現と一致しなくなり、未定義の動作が生じることがあります。 未定義の動作は、要素またはその機能の表示を妨げる​​だけでなく、アプリまたはサーバーにセキュリティ リスクをもたらす可能性もあります。

このガイダンスは、独自の JS 相互運用コードだけでなく、アプリが使用する JS ライブラリにも適用されます。これには、BootstrapJSjQuery などのサードパーティ製のフレームワークによって提供されるものも含まれます。

いくつかのドキュメントの例では、例の一部として、JS 相互運用機能を使用して要素を単なるデモンストレーションの目的で変換します。 そのような場合は、テキストに警告が表示されます。

詳しくは、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。

型関数のフィールドを持つ JavaScript クラス

型関数のフィールドを持つ JavaScript クラスは BlazorJS 相互運用でサポートされていません。 クラスの JavaScript 関数を使用してください。

サポート対象外:型関数のフィールドとしての次のクラスの GreetingHelpers.sayHello は、Blazor の JS 相互運用によって検出されず、C# コードから実行することはできません。

export class GreetingHelpers {
  sayHello = function() {
    ...
  }
}

サポート対象:関数として次のクラスの GreetingHelpers.sayHello はサポートされています。

export class GreetingHelpers {
  sayHello() {
    ...
  }
}

アロー関数もサポートされています。

export class GreetingHelpers {
  sayHello = () => {
    ...
  }
}

インライン イベント ハンドラーを回避する

JavaScript 関数は、インライン イベント ハンドラーから直接呼び出すことができます。 次の例で、alertUser は、ユーザーがボタンを選択したときに呼び出される JavaScript 関数です。

<button onclick="alertUser">Click Me!</button>

ただし、インライン イベント ハンドラーの使用は、JavaScript 関数呼び出しにおける設計のお粗末な選択です。

次の例に示すように、 addEventListener により JavaScript でハンドラーを割り当てる方法を選択して、インライン イベント ハンドラーを回避することをお勧めします。

AlertUser.razor.js:

export function alertUser() {
  alert('The button was selected!');
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", alertUser);
}

AlertUser.razor:

@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Alert User</h1>

<p>
    <button id="btn">Click Me!</button>
</p>

@code {
    private IJSObjectReference? module;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/AlertUser.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

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

詳細については、次のリソースを参照してください。

非同期 JavaScript 呼び出し

JS 相互運用呼び出しは、呼び出されたコードが同期であるか非同期であるかに関係なく、非同期となります。 呼び出しが非同期であるのは、サーバー側とクライアント側のレンダリング モデルでコンポーネントの互換性を確保するためです。 サーバー側レンダリングを採用する場合、JS 相互運用呼び出しはネットワーク接続を介して送信されるため、非同期である必要があります。 クライアント側レンダリングのみを採用するアプリの場合、同期 JS 相互運用呼び出しがサポートされます。

オブジェクトのシリアル化

Blazor では、次の要件と既定の動作でシリアル化に System.Text.Json を使用します。

  • 型には既定のコンストラクターが必要です。get/set アクセサーはパブリックである必要があります。フィールドはシリアル化されません。
  • グローバルな既定のシリアル化は、既存のコンポーネント ライブラリの破損、パフォーマンスとセキュリティへの影響、信頼性の低下を回避するためにカスタマイズできません。
  • .NET メンバー名をシリアル化すると、小文字の JSON キー名になります。
  • JSON は、大文字と小文字の混在が許可される、JsonElement C# インスタンスとして逆シリアル化されます。 C# モデル プロパティへの割り当ての内部キャストは、JSON キー名と C# プロパティ名の間に大文字と小文字の違いがあっても、期待したとおりに動作します。
  • KeyValuePair などの複雑なフレームワークの種類は発行時に IL Trimmer によってトリミングされ、JS の相互運用には存在しない可能性があります。 IL Trimmer でトリミングされる種類については、カスタムの種類を作成することをお勧めします。
  • Blazor は、C# のソース生成の使用時を含め、常に、JSON のシリアル化をリフレクションに依存します。 アプリのプロジェクト ファイルで JsonSerializerIsReflectionEnabledByDefaultfalse に設定すると、シリアル化の試行時にエラーが発生します。

JsonConverter API は、カスタム シリアル化に使用できます。 プロパティに [JsonConverter] 属性の注釈を付けて、既存のデータ型の既定のシリアル化をオーバーライドできます。

詳細については、.NET ドキュメントの次のリソースを参照してください。

Blazor では、Base64 へのバイト配列のエンコードおよびデコードを回避する、最適化されたバイト配列 JS 相互運用がサポートされています。 アプリで、カスタム シリアル化を適用し、結果のバイトを渡すことができます。 詳しくは、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。

Blazor では、大量の .NET オブジェクトが急速にシリアル化される場合、または大きな .NET オブジェクトまたは多数の .NET オブジェクトをシリアル化する必要がある場合に、マーシャリングされていない JS 相互運用がサポートされます。 詳しくは、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。

コンポーネントの破棄中の DOM クリーンアップ タスク

コンポーネントの破棄の間に、DOM クリーンアップ タスクに対して JS 相互運用コードを実行しないでください。 代わりに、次の理由により、クライアント上の JavaScript (JS) で MutationObserver パターンを使用してください。

  • クリーンアップ コードが Dispose{Async} で実行されるまでに、コンポーネントが DOM から削除されている可能性があります。
  • サーバー側のレンダリング中、クリーンアップ コードが Dispose{Async} で実行されるまでに、Blazor レンダラーがフレームワークによって破棄されている可能性があります。

MutationObserver パターンを使うと、DOM から要素が削除されていても関数を実行できます。

次の例では、DOMCleanup コンポーネントは次のようになります。

  • cleanupDivid を持つ <div> が含まれています。 <div> 要素は、コンポーネントが DOM から削除されるときに、コンポーネントのrestの DOM マークアップとともに DOM から削除されます。
  • DOMCleanup.razor.js ファイルから DOMCleanupJS クラスを読み込みし、その createObserver 関数を呼び出して MutationObserver コールバックを設定します。 これらのタスクは、OnAfterRenderAsync ライフサイクル メソッド で実行されます。

DOMCleanup.razor:

@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>DOM Cleanup Example</h1>

<div id="cleanupDiv"></div>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./Components/Pages/DOMCleanup.razor.js");

            await module.InvokeVoidAsync("DOMCleanup.createObserver");
        }
    }

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

次の例では、DOM 変更が発生するたびに MutationObserver コールバックが実行されます。 if ステートメントによってターゲット要素 (cleanupDiv) が削除されたことが確認されたら、クリーンアップ コードを実行します (if (targetRemoved) { ... })。 クリーンアップ コードの実行後にメモリ リークを避けるために、MutationObserver を切断して削除することが重要です。

DOMCleanup.razor.js は、前述の DOMCleanup コンポーネントと並べて配置されます。

export class DOMCleanup {
  static observer;

  static createObserver() {
    const target = document.querySelector('#cleanupDiv');

    this.observer = new MutationObserver(function (mutations) {
      const targetRemoved = mutations.some(function (mutation) {
        const nodes = Array.from(mutation.removedNodes);
        return nodes.indexOf(target) !== -1;
      });

      if (targetRemoved) {
        // Cleanup resources here
        // ...

        // Disconnect and delete MutationObserver
        this.observer && this.observer.disconnect();
        delete this.observer;
      }
    });

    this.observer.observe(target.parentNode, { childList: true });
  }
}

window.DOMCleanup = DOMCleanup;

回線を使用しない JavaScript 相互運用呼び出し

このセクションはサーバー側アプリにのみ適用されます。

JavaScript (JS) 相互運用呼び出しは、SignalR 回線が切断された後は発行できません。 コンポーネントの破棄中に回線がない、または回線が存在しないその他の時点で、次のメソッド呼び出しは失敗し、回線が切断されたというメッセージが JSDisconnectedException としてログされます。

JSDisconnectedException のログを回避したり、カスタム情報をログしたりするには、try-catch ステートメントで例外をキャッチします。

次のコンポーネント破棄の例で:

  • コンポーネントでは IAsyncDisposable を実装します。
  • objInstanceIJSObjectReference です。
  • JSDisconnectedException がキャッチされ、ログはされません。
  • 必要に応じて、任意のログ レベルで catch ステートメントにカスタム情報をログできます。 次の例では、コンポーネントの破棄中にいつ、どこで回線が切断されるかを開発者が気にしないことを前提としているため、カスタム情報はログされません。
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

回線が失われた後に、独自の JS オブジェクトをクリーンアップするか、クライアントで他の JS のコードを実行する必要がある場合は、クライアントの JS で MutationObserver のパターンを使用します。 MutationObserver パターンを使うと、DOM から要素が削除されていても関数を実行できます。

詳細については、次の記事を参照してください。

キャッシュされた JavaScript ファイル

JavaScript (JS) ファイルおよびその他の静的資産は、通常、Development 環境での開発中にクライアントにキャッシュされません。 開発中、静的資産要求には、値が no-cacheCache-Control ヘッダー、または値がゼロ (0) の max-age が含まれます。

Production 環境での運用中は、通常、JS ファイルはクライアントによってキャッシュされます。

ブラウザーでクライアント側のキャッシュを無効にするには、通常、開発者は次のいずれかのアプローチを採用します。

  • ブラウザーの開発者ツール コンソールが開いているときにキャッシュを無効にします。 ガイダンスについては、各ブラウザー メンテナンス ツールの開発者ツール ドキュメントを参照してください。
  • Blazor アプリの Web ページをブラウザーで手動で更新して、サーバーから JS ファイルを再読み込みします。 ASP.NET Core の HTTP キャッシング ミドルウェアでは、クライアントによって送信された有効なキャッシュなし Cache-Control ヘッダーが常に優先されます。

詳細については、以下を参照してください:

JavaScript 相互運用呼び出しのサイズ制限

このセクションは、サーバー側アプリの対話型コンポーネントにのみ適用されます。 クライアント側コンポーネントの場合、フレームワークは JavaScript (JS) 相互運用入力および出力のサイズに制限を課しません。

サーバー側アプリの対話型コンポーネントの場合、クライアントからサーバーにデータを渡す JS 相互運用呼び出しのサイズは、ハブ メソッドに許可される受信 SignalR メッセージの最大サイズによって制限され、これは HubOptions.MaximumReceiveMessageSize によって強制されます (既定値: 32 KB)。 JS から .NET への SignalR メッセージが MaximumReceiveMessageSize より大きい場合は、エラーがスローされます。 このフレームワークでは、ハブからクライアントへの SignalR メッセージのサイズが制限されることはありません。 サイズ制限、エラー メッセージ、メッセージ サイズ制限に対処するためのガイダンスの詳細については、「ASP.NET Core BlazorSignalRガイダンス」を参照してください。

アプリが実行されている場所を決定する

アプリが JS 相互運用呼び出しでコードが実行されている場所を知る必要がある場合は、OperatingSystem.IsBrowser を使用して WebAssembly 上のブラウザーのコンテキストでコンポーネントが実行されているかどうかを判断します。