次の方法で共有


ASP.NET Core Blazor で JavaScript 関数から .NET メソッドを呼び出す

注意

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

警告

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

重要

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

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

この記事では、JavaScript (JS) から .NET メソッドを呼び出す方法について説明します。

.NET から JS 関数を呼び出す方法については、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。

静的 .NET メソッドの呼び出し

JavaScript (JS) から静的 .NET メソッドを呼び出すには、JS 関数を使用します。

  • DotNet.invokeMethodAsync (推奨): サーバ側コンポーネントもクライアント側コンポーネントも非同期。
  • DotNet.invokeMethod: クライアント側コンポーネントのみ同期。

メソッドを含むアセンブリの名前、静的 .NET メソッドの識別子、および引数を渡します。

次に例を示します。

  • {ASSEMBLY NAME} プレースホルダーは、アプリのアセンブリ名です。
  • {.NET METHOD ID} プレースホルダーは .NET メソッド識別子です。
  • {ARGUMENTS} プレースホルダーは、メソッドに渡す省略可能なコンマ区切りの引数であり、各引数は JSON シリアル化可能である必要があります。
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS});

DotNet.invokeMethodAsync は、操作の結果を表す JS Promise を返します。 DotNet.invokeMethod (クライアント側コンポーネント) は操作の結果を返します。

重要

サーバー側コンポーネントでは、同期バージョン (invokeMethod) 経由よりも非同期関数 (invokeMethodAsync) を推奨します。

.NET メソッドはパブリックかつ静的であり、[JSInvokable] 属性を持つ必要があります。

次に例を示します。

  • {<T>} プレースホルダーは戻り値の型を示します。これは、値を返すメソッドにのみ必要です。
  • {.NET METHOD ID} プレースホルダーはメソッド識別子です。
@code {
    [JSInvokable]
    public static Task{<T>} {.NET METHOD ID}()
    {
        ...
    }
}

Note

オープン ジェネリック メソッドを呼び出すことは、静的な .NET メソッドではサポートされていませんが、インスタンス メソッドではサポートされています。 詳細については、「.NET ジェネリック クラス メソッドを呼び出す」のセクションを参照してください。

次のコンポーネントでは、ReturnArrayAsync C# メソッドによって int 配列が返されます。 [JSInvokable] 属性 がメソッドに適用され、JS によってメソッドが呼び出し可能になります。

CallDotnet1.razor:

@page "/call-dotnet-1"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 1</PageTitle>

<h1>Call .NET Example 1</h1>

<p>
    <button id="btn">Trigger .NET static method</button>
</p>

<p>
    See the result in the developer tools console.
</p>

@code {
    private IJSObjectReference? module;

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

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

    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync() =>
        Task.FromResult(new int[] { 11, 12, 13 });

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

CallDotnet1.razor.js:

export function returnArrayAsync() {
  DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
    .then(data => {
      console.log(data);
    });
}

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

addHandlersJS 関数は、ボタンに click イベントを追加します。 returnArrayAsyncJS 関数はハンドラーとして割り当てられます。

returnArrayAsyncJS 関数では、コンポーネントの ReturnArrayAsync .NET メソッドが呼び出され、その結果はブラウザーの Web 開発者ツール コンソールに記録されます。 BlazorSample はアプリのアセンブリ名です。

CallDotnet1.razor:

@page "/call-dotnet-1"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 1</PageTitle>

<h1>Call .NET Example 1</h1>

<p>
    <button id="btn">Trigger .NET static method</button>
</p>

<p>
    See the result in the developer tools console.
</p>

@code {
    private IJSObjectReference? module;

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

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

    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync() =>
        Task.FromResult(new int[] { 11, 12, 13 });

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

CallDotnet1.razor.js:

export function returnArrayAsync() {
  DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
    .then(data => {
      console.log(data);
    });
}

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

addHandlersJS 関数は、ボタンに click イベントを追加します。 returnArrayAsyncJS 関数はハンドラーとして割り当てられます。

returnArrayAsyncJS 関数では、コンポーネントの ReturnArrayAsync .NET メソッドが呼び出され、その結果はブラウザーの Web 開発者ツール コンソールに記録されます。 BlazorSample はアプリのアセンブリ名です。

CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

CallDotNetExample1.razor:

@page "/call-dotnet-example-1"

<h1>Call .NET Example 1</h1>

<p>
    <button onclick="returnArrayAsync()">
        Trigger .NET static method
    </button>
</p>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

<button> 要素の onclick HTML 属性は、 の @onclick ディレクティブ属性ではなく、Blazorclick イベントを処理するための JavaScript の onclick イベント ハンドラー割り当てです。 returnArrayAsyncJS 関数はハンドラーとして割り当てられます。

次の returnArrayAsyncJS 関数では、コンポーネントの ReturnArrayAsync .NET メソッドが呼び出され、その結果はブラウザーの Web 開発者ツール コンソールに記録されます。 BlazorSample はアプリのアセンブリ名です。

<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        console.log(data);
      });
    };
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

Trigger .NET static method ボタンが選択されている場合は、ブラウザーの開発者ツール コンソールの出力に配列データが表示されます。 出力の形式はブラウザーによって若干異なります。 次の出力は、Microsoft Edge で使用される形式を示しています。

Array(3) [ 11, 12, 13 ]

データを引数として渡して invokeMethodAsync 関数を呼び出すときに、.NET メソッドにデータを渡します。

.NET にデータを渡す方法を示すには、JS でメソッドが呼び出される ReturnArrayAsync メソッドに開始位置を渡します。

export function returnArrayAsync() {
  DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync', 14)
    .then(data => {
      console.log(data);
    });
}
<script>
  window.returnArrayAsync = () => {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync', 14)
      .then(data => {
        console.log(data);
      });
    };
</script>

コンポーネントの呼び出し可能 ReturnArrayAsync メソッドは開始位置を受け取り、そこから配列を構築します。 コンソールにログするために、配列が返されます。

[JSInvokable]
public static Task<int[]> ReturnArrayAsync(int startPosition) => 
    Task.FromResult(Enumerable.Range(startPosition, 3).ToArray());

アプリが再コンパイルされ、ブラウザーが更新されると、ボタンが選択されたときに、ブラウザーのコンソールに次の出力が表示されます。

Array(3) [ 14, 15, 16 ]

JS 呼び出しの .NET メソッド識別子は .NET メソッド名ですが、[JSInvokable] 属性コンストラクターを使用して別の識別子を指定することもできます。 次の例では、DifferentMethodNameReturnArrayAsync メソッドに割り当てられたメソッド識別子です。

[JSInvokable("DifferentMethodName")]

DotNet.invokeMethodAsync (サーバー側コンポーネントまたはクライアント側コンポーネント) または DotNet.invokeMethod (クライアント側コンポーネントのみ) の呼び出しでは、DifferentMethodName を呼び出して ReturnArrayAsync .NETメソッドを実行します。

  • DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
  • DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (クライアント側コンポーネントのみ)

Note

このセクションの ReturnArrayAsync メソッドの例では、明示的な C# async および await キーワードを使用せずに Task の結果を返します。 async および await を使用したメソッドのコーディングは、await キーワードを使用して非同期操作の値を返す一般的なメソッドです。

async および await キーワードで構成される ReturnArrayAsync メソッド:

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync() => 
    await Task.FromResult(new int[] { 11, 12, 13 });

詳細については、C# ガイドの「Async および Await を使用した非同期プログラミング」を参照してください。

.NET に渡す JavaScript オブジェクトとデータ参照を作成する

DotNet.createJSObjectReference(jsObject) を呼び出して、.NET に渡すことができるように JS オブジェクト参照を構築します。jsObject は JS オブジェクト参照の作成に使われる JS Object です。 次の例では、シリアル化できない window オブジェクトへの参照を .NET に渡します。これはその参照を ReceiveWindowObject C# メソッドで IJSObjectReference として受け取ります。

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', 'ReceiveWindowObject', 
  DotNet.createJSObjectReference(window));
[JSInvokable]
public static void ReceiveWindowObject(IJSObjectReference objRef)
{
    ...
}

前の例で、{ASSEMBLY NAME} プレースホルダーはアプリの名前空間です。

Note

前の例では、window オブジェクトへの参照が JS に保持されないため、JSObjectReference の破棄は必要ありません。

JSObjectReference への参照を維持したら、クライアントでの JS メモリのリークを避けるため、それを破棄する必要があります。 次の例では、前のコードをリファクタリングして JSObjectReference への参照をキャプチャした後、DotNet.disposeJSObjectReference() を呼び出して参照を破棄します。

var jsObjectReference = DotNet.createJSObjectReference(window);

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', 'ReceiveWindowObject', jsObjectReference);

DotNet.disposeJSObjectReference(jsObjectReference);

前の例で、{ASSEMBLY NAME} プレースホルダーはアプリの名前空間です。

DotNet.createJSStreamReference(streamReference) を呼び出して JS ストリーム参照を構築し、.NET に渡すことができるようにします。streamReference は、ArrayBufferBlob、または Uint8ArrayFloat32Array などの任意の型指定された配列であり、JS ストリーム参照を作成するために使用されます。

インスタンス .NET メソッドの呼び出し

JavaScript (JS) からインスタンス .NET メソッドを呼び出す場合:

  • インスタンスを DotNetObjectReference でラップし、その上で Create を呼び出すことにより、JS を参照して .NET インスタンスを渡します。

  • 渡された DotNetObjectReferenceinvokeMethodAsync (推奨) または invokeMethod (クライアント側コンポーネントのみ) を使用して、JS から .NET インスタンス メソッドを呼び出します。 インスタンス .NET メソッドの識別子とすべての引数を渡します。 .NET インスタンスは、JS から他の .NET メソッドを呼び出すときに引数として渡すこともできます。

    次に例を示します。

    • dotNetHelperDotNetObjectReference です。
    • {.NET METHOD ID} プレースホルダーは .NET メソッド識別子です。
    • {ARGUMENTS} プレースホルダーは、メソッドに渡す省略可能なコンマ区切りの引数であり、各引数は JSON シリアル化可能である必要があります。
    dotNetHelper.invokeMethodAsync('{.NET METHOD ID}', {ARGUMENTS});
    

    Note

    インスタンス メソッドを呼び出すときは、invokeMethodAsyncinvokeMethod はアセンブリ名パラメーターを受け付けません。

    invokeMethodAsync は、操作の結果を表す JS Promise を返します。 invokeMethod (クライアント側コンポーネントのみ) は操作の結果を返します。

    重要

    サーバー側コンポーネントでは、同期バージョン (invokeMethod) 経由よりも非同期関数 (invokeMethodAsync) を推奨します。

  • DotNetObjectReference の破棄。

この記事の以降のセクションでは、インスタンス .NET メソッドを呼び出すさまざまな方法について説明します。

JavaScript で呼び出し可能な .NET メソッドのトリミングを回避する

このセクションは、Ahead-Of-Time (AOT) コンパイルランタイム再リンクが有効になっているクライアント側アプリに適用されます。

次のセクションに示すいくつかの例は、クラス インスタンスのアプローチに基づいています。ここで、[JSInvokable] 属性でマークされていて、JavaScript で呼び出し可能な .NET メソッドは、Razor コンポーネントではないクラスのメンバーです。 このような .NET メソッドがコンポーネント内にある Razor 場合、それらは、ランタイムの再リンクやトリミングから保護されます。 .NET メソッドが Razor コンポーネントの外部でトリミングされないようにするには、次の例に示すように、クラスのコンストラクターに DynamicDependency 属性を使用してメソッドを実装します。

using System.Diagnostics.CodeAnalysis;
using Microsoft.JSInterop;

public class ExampleClass {

    [DynamicDependency(nameof(ExampleJSInvokableMethod))]
    public ExampleClass()
    {
    }

    [JSInvokable]
    public string ExampleJSInvokableMethod()
    {
        ...
    }
}

詳細については、トリミング用に .NET ライブラリを準備する: DynamicDependency に関するページを参照してください。

DotNetObjectReference を個々の JavaScript 関数に渡す

このセクションの例は、DotNetObjectReference を個々の JavaScript (JS) 関数に渡す方法を示しています。

次の sayHello1JS 関数では DotNetObjectReference を受け取り、invokeMethodAsync を呼び出してコンポーネントの GetHelloMessage .NET メソッドを呼び出します。

<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

次の コンポーネントの場合:

  • コンポーネントには、GetHelloMessage という名前の JS 呼び出し可能な .NET メソッドが含まれます。
  • Trigger .NET instance method ボタンを選択すると、JS 関数 sayHello1DotNetObjectReference と一緒に呼び出されます。
  • sayHello1:
    • GetHelloMessage を呼び出し、メッセージ結果を受信します。
    • メッセージの結果を呼び出し元の TriggerDotNetInstanceMethod メソッドに返します。
  • resultsayHello1 から返されたメッセージがユーザーに表示されます。
  • メモリ リークを回避し、ガベージ コレクションを許可するには、DotNetObjectReference によって作成された .NET オブジェクト参照は Dispose メソッドで破棄されます。

CallDotnet2.razor:

@page "/call-dotnet-2"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 2</PageTitle>

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotnet2>? objRef;

    protected override void OnInitialized() =>
        objRef = DotNetObjectReference.Create(this);

    public async Task TriggerDotNetInstanceMethod() =>
        result = await JS.InvokeAsync<string>("sayHello1", objRef);

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

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

CallDotnet2.razor:

@page "/call-dotnet-2"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 2</PageTitle>

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotnet2>? objRef;

    protected override void OnInitialized() =>
        objRef = DotNetObjectReference.Create(this);

    public async Task TriggerDotNetInstanceMethod() =>
        result = await JS.InvokeAsync<string>("sayHello1", objRef);

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

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

CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample2>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample2.razor:

@page "/call-dotnet-example-2"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 2</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample2> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

インスタンス メソッドに引数を渡すには、以下のガイダンスを使用します。

.NET メソッドの呼び出しにパラメーターを追加します。 次の例では、名前がメソッドに渡されます。 必要に応じて、追加のパラメーターを一覧に追加します。

<script>
  window.sayHello2 = (dotNetHelper, name) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
  };
</script>

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

.NET メソッドにパラメーター リストを指定します。

CallDotnet3.razor:

@page "/call-dotnet-3"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 3</PageTitle>

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotnet3>? objRef;

    protected override void OnInitialized() => 
        objRef = DotNetObjectReference.Create(this);

    public async Task TriggerDotNetInstanceMethod() =>
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

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

CallDotnet3.razor:

@page "/call-dotnet-3"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 3</PageTitle>

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotnet3>? objRef;

    protected override void OnInitialized() => 
        objRef = DotNetObjectReference.Create(this);

    public async Task TriggerDotNetInstanceMethod() =>
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

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

CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private DotNetObjectReference<CallDotNetExample3>? objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

CallDotNetExample3.razor:

@page "/call-dotnet-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call .NET Example 3</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private DotNetObjectReference<CallDotNetExample3> objRef;

    protected override void OnInitialized()
    {
        objRef = DotNetObjectReference.Create(this);
    }

    public async Task TriggerDotNetInstanceMethod()
    {
        result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
    }

    [JSInvokable]
    public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

複数の JavaScript 関数を持つクラスに DotNetObjectReference を渡す

このセクションの例は、DotNetObjectReference を複数の関数を持つ JavaScript (JS) クラスに渡す方法を示しています。

DotNetObjectReference を作成して OnAfterRenderAsync ライフサイクル メソッド から JS クラスに渡して、複数の関数で使用できるようにします。 次の例に示すように、.NET コードで DotNetObjectReference が破棄されることを確認します。

次のコンポーネントでは、[Trigger JS function] ボタンは、Blazor の @onclick ディレクティブ属性ではなく、JSonclick プロパティを設定して JS を呼び出します。

CallDotNetExampleOneHelper.razor:

@page "/call-dotnet-example-one-helper"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button id="sayHelloBtn">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button id="welcomeVisitorBtn">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code {
    private IJSObjectReference? module;
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

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

            dotNetHelper = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);

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

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, {name}!";

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

        dotNetHelper?.Dispose();
    }
}

前の例の場合:

  • JS は、挿入された IJSRuntime インスタンスです。 IJSRuntime は Blazor フレームワークによって登録されます。
  • 変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。
  • コンポーネントは、ガベージ コレクションを許可し、メモリ リークを防ぐために、DotNetObjectReference を明示的に破棄する必要があります。
  • JSDisconnectedException は、 Blazorの SignalR 回路が失われた場合に、モジュールの破棄中にトラップされます。 上記のコードが Blazor WebAssembly アプリで使用されている場合、失われる SignalR 接続がないため、 try-catch ブロックを削除し、モジュールを破棄する行 (await module.DisposeAsync();) のままにすることができます。 詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

CallDotNetExampleOneHelper.razor.js:

export class GreetingHelpers {
  static dotNetHelper;

  static setDotNetHelper(value) {
    GreetingHelpers.dotNetHelper = value;
  }

  static async sayHello() {
    const msg =
      await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
    alert(`Message from .NET: "${msg}"`);
  }

  static async welcomeVisitor() {
    const msg =
      await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
    alert(`Message from .NET: "${msg}"`);
  }
}

export function addHandlers() {
  const sayHelloBtn = document.getElementById("sayHelloBtn");
  sayHelloBtn.addEventListener("click", GreetingHelpers.sayHello);

  const welcomeVisitorBtn = document.getElementById("welcomeVisitorBtn");
  welcomeVisitorBtn.addEventListener("click", GreetingHelpers.welcomeVisitor);
}

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
    <label>
        Message: <input @bind="name" />
    </label>
</p>

<p>
    <button onclick="GreetingHelpers.sayHello()">
        Trigger JS function <code>sayHello</code>
    </button>
</p>

<p>
    <button onclick="GreetingHelpers.welcomeVisitor()">
        Trigger JS function <code>welcomeVisitor</code>
    </button>
</p>

@code {
    private string? name;
    private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            dotNetHelper = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", 
                dotNetHelper);
        }
    }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {name}!";

    [JSInvokable]
    public string GetWelcomeMessage() => $"Welcome, {name}!";

    public void Dispose()
    {
        dotNetHelper?.Dispose();
    }
}

前の例の場合:

  • JS は、挿入された IJSRuntime インスタンスです。 IJSRuntime は Blazor フレームワークによって登録されます。
  • 変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。
  • コンポーネントは、ガベージ コレクションを許可し、メモリ リークを防ぐために、DotNetObjectReference を明示的に破棄する必要があります。
<script>
  class GreetingHelpers {
    static dotNetHelper;

    static setDotNetHelper(value) {
      GreetingHelpers.dotNetHelper = value;
    }

    static async sayHello() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
      alert(`Message from .NET: "${msg}"`);
    }

    static async welcomeVisitor() {
      const msg = 
        await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
      alert(`Message from .NET: "${msg}"`);
    }
  }

  window.GreetingHelpers = GreetingHelpers;
</script>

前の例の場合:

  • GreetingHelpers クラスは、window オブジェクトに追加され、クラスをグローバルに定義します。これにより、Blazor が JS 相互運用のためにクラスを見つけることができます。
  • 変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

Note

JS の場所に関する一般的なガイダンスと、実稼働アプリの推奨事項については、「ASP.NET Core Blazor アプリでの JavaScript の場所」を参照してください。

NET ジェネリック クラス メソッドを呼び出す

JavaScript (JS) 関数は .NET ジェネリック クラス メソッドを呼び出すことができ、ここで JS 関数はジェネリック クラスの .NET メソッドを呼び出します。

次のジェネリック型クラス (GenericType<TValue>) で:

  • クラスには、1 つのジェネリック Value プロパティを持つ 1 つの型パラメーター (TValue) があります。
  • クラスには、[JSInvokable] 属性でマークされた 2 つの非ジェネリック メソッドがあり、それぞれに newValue という名前のジェネリック型パラメーターがあります。
    • UpdatenewValue から Value の値を同期的に更新します。
    • UpdateAsync は、待ち受け時に現在のコンテキストに非同期的に戻す Task.Yield で待ち受け可能なタスクを作成した後 newValue から Value の値を非同期的に更新します。
  • クラスのメソッドはそれぞれ、TValue の型と Value の値をコンソールに書き込みます。 コンソールへの書き込みは、デモンストレーションのみを目的とします。 通常、実稼働アプリでは、アプリのログのため、コンソールへの書き込みを回避します。 詳細については、「ASP.NET Core Blazor のログ」および「.NET Core および ASP.NET Core でのログ記録」を参照してください。

Note

"オープン ジェネリック型とメソッド" では、型プレースホルダーの型は指定されません。 逆に、"クローズド ジェネリック" では、すべての型プレースホルダーの型が指定されます。 このセクションの例は、クローズド ジェネリックを示していますが、オープン ジェネリックでの JS 相互運用 "インスタンス メソッド" の呼び出しは "サポートされています"。 オープン ジェネリックの使用は、この記事で前述した 静的 .NET メソッドの呼び出しではサポートされていません。

詳細については、次の記事をご覧ください。

GenericType.cs:

using Microsoft.JSInterop;

public class GenericType<TValue>
{
    public TValue? Value { get; set; }

    [JSInvokable]
    public void Update(TValue newValue)
    {
        Value = newValue;

        Console.WriteLine($"Update: GenericType<{typeof(TValue)}>: {Value}");
    }

    [JSInvokable]
    public async Task UpdateAsync(TValue newValue)
    {
        await Task.Yield();
        Value = newValue;

        Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>: {Value}");
    }
}

次の invokeMethodsAsync 関数では:

  • ジェネリック型クラスの UpdateUpdateAsync のメソッドは、文字列と数値を表す引数で呼び出されます。
  • クライアント側コンポーネントでは、invokeMethod との同期的な .NET メソッドの呼び出しがサポートされています。 syncInterop は、クライアントで JS 相互運用が発生するかどうかを示すブール値を受け取ります。 syncInteroptrue の場合、invokeMethod は安全に呼び出されます。 syncInterop の値が false の場合、JS 相互運用がサーバー側コンポーネントで実行されているため、非同期関数 invokeMethodAsync だけが呼び出されます。
  • デモンストレーションの目的で、DotNetObjectReference 関数呼び出し (invokeMethod または invokeMethodAsync)、呼び出された .NET メソッド (Update または UpdateAsync) およびおよび引数がコンソールに書き込まれます。 引数は乱数を使用して JS 関数呼び出しと .NET メソッド呼び出しの照合を許可します (.NET 側のコンソールにも書き込まれます)。 通常、実稼働コードは、クライアントまたはサーバー上でコンソールに書き込まれません。 実稼働アプリは通常、アプリのログに依存します。 詳細については、「ASP.NET Core Blazor のログ」および「.NET Core および ASP.NET Core でのログ記録」を参照してください。
<script>
  const randomInt = () => Math.floor(Math.random() * 99999);

  window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => {
    var n = randomInt();
    console.log(`JS: invokeMethodAsync:Update('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);
    await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update('string ${n}')`);
      dotNetHelper1.invokeMethod('Update', `string ${n}`);
    }

    n = randomInt();
    console.log(`JS: invokeMethodAsync:Update(${n})`);
    await dotNetHelper2.invokeMethodAsync('Update', n);

    n = randomInt();
    console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);
    await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

    if (syncInterop) {
      n = randomInt();
      console.log(`JS: invokeMethod:Update(${n})`);
      dotNetHelper2.invokeMethod('Update', n);
    }
  };
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

次の GenericsExample コンポーネントでは、以下のことを行います。

  • Invoke Interop ボタンを選択すると、JS 関数 invokeMethodsAsync が呼び出されます。
  • DotNetObjectReference 型のペアが作成され、string および int として GenericType のインスタンスの JS 関数に渡されます。

GenericsExample.razor:

@page "/generics-example"
@implements IDisposable
@inject IJSRuntime JS

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code {
    private GenericType<string> genericType1 = new() { Value = "string 0" };
    private GenericType<int> genericType2 = new() { Value = 0 };
    private DotNetObjectReference<GenericType<string>>? objRef1;
    private DotNetObjectReference<GenericType<int>>? objRef2;

    protected override void OnInitialized()
    {
        objRef1 = DotNetObjectReference.Create(genericType1);
        objRef2 = DotNetObjectReference.Create(genericType2);
    }

    public async Task InvokeInterop()
    {
        var syncInterop = OperatingSystem.IsBrowser();

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync", syncInterop, objRef1, objRef2);
    }

    public void Dispose()
    {
        objRef1?.Dispose();
        objRef2?.Dispose();
    }
}
@page "/generics-example"
@implements IDisposable
@inject IJSRuntime JS

<p>
    <button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
    <li>genericType1: @genericType1?.Value</li>
    <li>genericType2: @genericType2?.Value</li>
</ul>

@code {
    private GenericType<string> genericType1 = new() { Value = "string 0" };
    private GenericType<int> genericType2 = new() { Value = 0 };
    private DotNetObjectReference<GenericType<string>>? objRef1;
    private DotNetObjectReference<GenericType<int>>? objRef2;

    protected override void OnInitialized()
    {
        objRef1 = DotNetObjectReference.Create(genericType1);
        objRef2 = DotNetObjectReference.Create(genericType2);
    }

    public async Task InvokeInterop()
    {
        var syncInterop = OperatingSystem.IsBrowser();

        await JS.InvokeVoidAsync(
            "invokeMethodsAsync", syncInterop, objRef1, objRef2);
    }

    public void Dispose()
    {
        objRef1?.Dispose();
        objRef2?.Dispose();
    }
}

前の例の JS は、挿入された IJSRuntime インスタンスです。 IJSRuntime は Blazor フレームワークによって登録されます。

クライアント側コンポーネントで Invoke Interop ボタンが選択されている場合の、前の例の一般的な出力を次に示します。

JS: invokeMethodAsync:Update('string 37802')
.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync('string 53051')
JS: invokeMethod:Update('string 26784')
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

前の例がサーバー側コンポーネントに実装されている場合、invokeMethod を使用した同期呼び出しは回避されます。 サーバー側コンポーネントでは、同期バージョン (invokeMethod) 経由よりも非同期関数 (invokeMethodAsync) を推奨します。

サーバー側コンポーネントの一般的な出力

JS: invokeMethodAsync:Update('string 34809')
.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync('string 93059')
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

上記の出力例は、スレッドのスケジューリングやメソッドの実行速度など、いくつかの要因に応じて、非同期メソッドが "任意の順序で" 実行され、完了する例を示しています。 非同期メソッド呼び出しの完了順序は、確実には予測できません。

クラス インスタンスの例

次の sayHello1JS 関数:

  • 渡された DotNetObjectReferenceGetHelloMessage .NET メソッドを呼び出します。
  • GetHelloMessage から sayHello1 の呼び出し元にメッセージを返します。
<script>
  window.sayHello1 = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
  };
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

次の HelloHelper クラスには、GetHelloMessage という名前の JS 呼び出し可能な .NET メソッドが含まれます。 HelloHelper が作成されると、Name プロパティの名前が GetHelloMessage からのメッセージを返すために使用されます。

HelloHelper.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class HelloHelper(string? name)
{
    public string? Name { get; set; } = name ?? "No Name";

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class HelloHelper(string? name)
{
    public string? Name { get; set; } = name ?? "No Name";

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No Name";
    }

    public string? Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string? name)
    {
        Name = name ?? "No Name";
    }

    public string? Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}
using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    [JSInvokable]
    public string GetHelloMessage() => $"Hello, {Name}!";
}

次の JsInteropClasses3 クラスの CallHelloHelperGetHelloMessage メソッドでは、HelloHelper の新しいインスタンスで JS 関数 sayHello1 を呼び出します。

JsInteropClasses3.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses3(IJSRuntime js)
{
    private readonly IJSRuntime js = js;

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses3(IJSRuntime js)
{
    private readonly IJSRuntime js = js;

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string? name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses3
{
    private readonly IJSRuntime js;

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

    public async ValueTask<string> CallHelloHelperGetHelloMessage(string name)
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        return await js.InvokeAsync<string>("sayHello1", objRef);
    }
}

メモリ リークを回避し、ガベージ コレクションを許可するため、DotNetObjectReference によって作成された .NET オブジェクト参照は、そのオブジェクト参照が using var 構文でスコープ外になったときに破棄されます。

次のコンポーネントで [Trigger .NET instance method] ボタンを選択すると、name の値で JsInteropClasses3.CallHelloHelperGetHelloMessage が呼び出されます。

CallDotnet4.razor:

@page "/call-dotnet-4"
@inject IJSRuntime JS

<PageTitle>Call .NET 4</PageTitle>

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized() => 
        jsInteropClasses = new JsInteropClasses3(JS);

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

CallDotnet4.razor:

@page "/call-dotnet-4"
@inject IJSRuntime JS

<PageTitle>Call .NET 4</PageTitle>

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized() => 
        jsInteropClasses = new JsInteropClasses3(JS);

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;
    private JsInteropClasses3? jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        if (jsInteropClasses is not null)
        {
            result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
        }
    }
}

CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    }
}

CallDotNetExample4.razor:

@page "/call-dotnet-example-4"
@inject IJSRuntime JS

<h1>Call .NET Example 4</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;
    private JsInteropClasses3 jsInteropClasses;

    protected override void OnInitialized()
    {
        jsInteropClasses = new JsInteropClasses3(JS);
    }

    private async Task TriggerDotNetInstanceMethod()
    {
        result = await jsInteropClasses.CallHelloHelperGetHelloMessage(name);
    }
}

次の図は、Name フィールドに Amy Pond という名前でレンダリングされたコンポーネントを示しています。 ボタンを選択すると、UI に Hello, Amy Pond! が表示されます。

レンダリングされた 'CallDotNetExample4' コンポーネントの例

JsInteropClasses3 クラスに示されている上記のパターンは、完全にコンポーネントに実装することもできます。

CallDotnet5.razor:

@page "/call-dotnet-5"
@inject IJSRuntime JS

<PageTitle>Call .NET 5</PageTitle>

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

CallDotnet5.razor:

@page "/call-dotnet-5"
@inject IJSRuntime JS

<PageTitle>Call .NET 5</PageTitle>

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string? name;
    private string? result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

CallDotNetExample5.razor:

@page "/call-dotnet-example-5"
@inject IJSRuntime JS

<h1>Call .NET Example 5</h1>

<p>
    <label>
        Name: <input @bind="name" />
    </label>
</p>

<p>
    <button @onclick="TriggerDotNetInstanceMethod">
        Trigger .NET instance method
    </button>
</p>

<p>
    @result
</p>

@code {
    private string name;
    private string result;

    public async Task TriggerDotNetInstanceMethod()
    {
        using var objRef = DotNetObjectReference.Create(new HelloHelper(name));
        result = await JS.InvokeAsync<string>("sayHello1", objRef);
    }
}

メモリ リークを回避し、ガベージ コレクションを許可するため、DotNetObjectReference によって作成された .NET オブジェクト参照は、そのオブジェクト参照が using var 構文でスコープ外になったときに破棄されます。

name フィールドに名前 Amy Pond が指定されている場合、コンポーネントによって表示される出力は Hello, Amy Pond! です。

前の コンポーネントでは、.NET オブジェクト参照は破棄されます。 クラスまたはコンポーネントが DotNetObjectReference を破棄しない場合は、渡された DotNetObjectReferencedispose を呼び出して、クライアントから破棄します。

window.{JS FUNCTION NAME} = (dotNetHelper) => {
  dotNetHelper.invokeMethodAsync('{.NET METHOD ID}');
  dotNetHelper.dispose();
}

前の例の場合:

  • {JS FUNCTION NAME} プレースホルダーは JS 関数の名前です。
  • 変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。
  • {.NET METHOD ID} プレースホルダーは .NET メソッド識別子です。

コンポーネント インスタンス .NET メソッド ヘルパー クラス

ヘルパー クラスでは、.NET インスタンス メソッドを Action として呼び出すことができます。 ヘルパー クラスは、静的な .NET メソッドを使用できないシナリオで役立ちます。

  • 同じ種類の複数のコンポーネントが同じページにレンダリングされる場合。
  • サーバー側アプリで、複数のユーザーが同じコンポーネントを同時に使用する場合。

次に例を示します。

  • コンポーネントには、いくつかの ListItem1 コンポーネントが含まれています。
  • ListItem1 コンポーネントは、メッセージとボタンで構成されます。
  • ListItem1 コンポーネント ボタンが選択されると、その ListItem1UpdateMessage メソッドによってリスト項目のテキストが変更され、ボタンが非表示になります。

次の MessageUpdateInvokeHelper クラスでは、クラスがインスタンス化されるときに指定された Action を呼び出すために、JS 呼び出し可能な .NET メソッド UpdateMessageCaller が保持されます。

MessageUpdateInvokeHelper.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class MessageUpdateInvokeHelper(Action action)
{
    private readonly Action action = action;

    [JSInvokable]
    public void UpdateMessageCaller() => action.Invoke();
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class MessageUpdateInvokeHelper(Action action)
{
    private readonly Action action = action;

    [JSInvokable]
    public void UpdateMessageCaller() => action.Invoke();
}
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}
using System;
using Microsoft.JSInterop;

public class MessageUpdateInvokeHelper
{
    private Action action;

    public MessageUpdateInvokeHelper(Action action)
    {
        this.action = action;
    }

    [JSInvokable]
    public void UpdateMessageCaller()
    {
        action.Invoke();
    }
}

次の updateMessageCallerJS 関数は、UpdateMessageCaller .NET メソッドを呼び出します。

<script>
  window.updateMessageCaller = (dotNetHelper) => {
    dotNetHelper.invokeMethodAsync('UpdateMessageCaller');
    dotNetHelper.dispose();
  }
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

次の ListItem1 コンポーネントは、親コンポーネントで何度でも使用できる共有コンポーネントであり、HTML リスト (<ul>...</ul> または <ol>...</ol>) のリスト項目 (<li>...</li>) を作成します。 各 ListItem1 コンポーネント インスタンスは、Action がその UpdateMessage メソッドに設定された MessageUpdateInvokeHelper のインスタンスを確立します。

ListItem1 コンポーネントの InteropCall ボタンが選択された場合、MessageUpdateInvokeHelper インスタンス用に作成された DotNetObjectReferenceupdateMessageCaller が呼び出されます。 これにより、フレームワークはその ListItem1MessageUpdateInvokeHelper インスタンスで UpdateMessageCaller を呼び出すことができます。 渡された DotNetObjectReference は JS (dotNetHelper.dispose()) で破棄されます。

ListItem1.razor:

@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        if (messageUpdateInvokeHelper is not null)
        {
            await JS.InvokeVoidAsync("updateMessageCaller",
                DotNetObjectReference.Create(messageUpdateInvokeHelper));
        }
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}
@inject IJSRuntime JS

<li>
    @message
    <button @onclick="InteropCall" style="display:@display">InteropCall</button>
</li>

@code {
    private string message = "Select one of these list item buttons.";
    private string display = "inline-block";
    private MessageUpdateInvokeHelper messageUpdateInvokeHelper;

    protected override void OnInitialized()
    {
        messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);
    }

    protected async Task InteropCall()
    {
        await JS.InvokeVoidAsync("updateMessageCaller",
            DotNetObjectReference.Create(messageUpdateInvokeHelper));
    }

    private void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }
}

messageUpdateMessage で設定されている場合、UI を更新するために StateHasChanged が呼び出されます。 StateHasChanged が呼び出されていない場合、Blazor には Action が呼び出されたときに UI を更新する必要があることを知る方法はありません。

次の親コンポーネントには、それぞれ ListItem1 コンポーネントのインスタンスである 4 つのリスト項目が含まれています。

CallDotnet6.razor:

@page "/call-dotnet-6"

<PageTitle>Call .NET 6</PageTitle>

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

CallDotnet6.razor:

@page "/call-dotnet-6"

<PageTitle>Call .NET 6</PageTitle>

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

CallDotNetExample6.razor:

@page "/call-dotnet-example-6"

<h1>Call .NET Example 6</h1>

<ul>
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
    <ListItem1 />
</ul>

次の図は、2 番目の InteropCall ボタンが選択された後にレンダリングされた親コンポーネントを示しています。

  • 2 番目の ListItem1 コンポーネントによって UpdateMessage Called! メッセージが表示されました。
  • ボタンの CSS display プロパティが none に設定されているため、2 番目の ListItem1 コンポーネントの InteropCall ボタンは表示されません。

レンダリングされた 'CallDotNetExample6' コンポーネントの例

要素プロパティに割り当てられた DotNetObjectReference から呼び出されるコンポーネント インスタンスの .NET メソッド

HTML 要素のプロパティに対する DotNetObjectReference の割り当てにより、コンポーネント インスタンスの .NET メソッドを呼び出すことができます。

Component インスタンス .NET メソッド ヘルパー クラスセクションで説明されている方法と同様に、このアプローチは、静的な .NET メソッドを使用できないシナリオで役立ちます。

  • 同じ種類の複数のコンポーネントが同じページにレンダリングされる場合。
  • サーバー側アプリで、複数のユーザーが同じコンポーネントを同時に使用する場合。
  • .NET メソッドが、Blazor イベント (たとえば @onclick) からではなく、JS イベント (たとえば onclick) から呼び出される場合。

次に例を示します。

  • コンポーネントには、共有コンポーネントであるいくつかの ListItem2 コンポーネントが含まれています。
  • ListItem2 コンポーネントは、リスト項目メッセージ <span> と、display CSS プロパティが表示用に inline-block に設定された 2 つ目の <span> で構成されています。
  • ListItem2 コンポーネントのリスト項目が選択されると、その ListItem2UpdateMessage メソッドによって最初の <span> のリスト項目テキストが変更され、その display プロパティを none に設定することで 2 つ目の <span> が非表示になります。

次の assignDotNetHelperJS 関数は、dotNetHelper という名前のプロパティの要素に DotNetObjectReference を割り当てます。 次の interopCallJS 関数では、渡される要素に DotNetObjectReference を使用して、UpdateMessage という名前の .NET メソッドを呼び出します。

ListItem2.razor.js:

export function assignDotNetHelper(element, dotNetHelper) {
  element.dotNetHelper = dotNetHelper;
}

export async function interopCall(element) {
  await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}

ListItem2.razor.js:

export function assignDotNetHelper(element, dotNetHelper) {
  element.dotNetHelper = dotNetHelper;
}

export async function interopCall(element) {
  await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}
<script>
  window.assignDotNetHelper = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
  }

  window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
  }
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

前の例で、変数名 dotNetHelper は任意であり、任意の希望する名前に変更できます。

次の ListItem2 コンポーネントは、親コンポーネントで何度でも使用できる共有コンポーネントであり、HTML リスト (<ul>...</ul> または <ol>...</ol>) のリスト項目 (<li>...</li>) を作成します。

ListItem2 コンポーネント インスタンスでは、要素の参照 (リスト項目の最初の <span> 要素) とコンポーネント インスタンスを DotNetObjectReference として、OnAfterRenderAsyncassignDotNetHelperJS 関数を呼び出します。

ListItem2 コンポーネントのメッセージ <span> が選択されると、<span> 要素をパラメーター (this) として渡して interopCall が呼び出されます。これにより、UpdateMessage .NET メソッドが呼び出されます。 UpdateMessage では、message が設定され、2 つ目の <span>display プロパティが更新されると、StateHasChanged が呼び出されて UI が更新されます。 StateHasChanged が呼び出されないと、Blazor にはメソッドが呼び出されたときに UI を更新する必要があることを知る方法がありません。

DotNetObjectReference は、コンポーネントが破棄されるときに破棄されます。

ListItem2.razor:

@inject IJSRuntime JS
@implements IAsyncDisposable

<li>
    <span style="font-weight:bold;color:@color" @ref="elementRef"
        @onclick="CallJSToInvokeDotnet">
        @message
    </span>
    <span style="display:@display">
        Not Updated Yet!
    </span>
</li>

@code {
    private IJSObjectReference? module;
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";
    private string color = "initial";

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

            objRef = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    public async Task CallJSToInvokeDotnet()
    {
        if (module is not null)
        {
            await module.InvokeVoidAsync("interopCall", elementRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        color = "MediumSeaGreen";
        StateHasChanged();
    }

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

        objRef?.Dispose();
    }
}
@inject IJSRuntime JS
@implements IAsyncDisposable

<li>
    <span style="font-weight:bold;color:@color" @ref="elementRef"
        @onclick="CallJSToInvokeDotnet">
        @message
    </span>
    <span style="display:@display">
        Not Updated Yet!
    </span>
</li>

@code {
    private IJSObjectReference? module;
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";
    private string color = "initial";

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

            objRef = DotNetObjectReference.Create(this);
            await module.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    public async Task CallJSToInvokeDotnet()
    {
        if (module is not null)
        {
            await module.InvokeVoidAsync("interopCall", elementRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        color = "MediumSeaGreen";
        StateHasChanged();
    }

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

        objRef?.Dispose();
    }
}
@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2>? objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2> objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

    public void Dispose() => objRef?.Dispose();
}
@inject IJSRuntime JS

<li>
    <span @ref="elementRef" onclick="interopCall(this)">@message</span>
    <span style="display:@display">Not Updated Yet!</span>
</li>

@code {
    private DotNetObjectReference<ListItem2> objRef;
    private ElementReference elementRef;
    private string display = "inline-block";
    private string message = "Select one of these list items.";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JS.InvokeVoidAsync("assignDotNetHelper", elementRef, objRef);
        }
    }

    [JSInvokable]
    public void UpdateMessage()
    {
        message = "UpdateMessage Called!";
        display = "none";
        StateHasChanged();
    }

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

次の親コンポーネントには、それぞれ ListItem2 コンポーネントのインスタンスである 4 つのリスト項目が含まれています。

CallDotnet7.razor:

@page "/call-dotnet-7"

<PageTitle>Call .NET 7</PageTitle>

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

CallDotnet7.razor:

@page "/call-dotnet-7"

<PageTitle>Call .NET 7</PageTitle>

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

CallDotNetExample7.razor:

@page "/call-dotnet-example-7"

<h1>Call .NET Example 7</h1>

<ul>
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
    <ListItem2 />
</ul>

クライアント側コンポーネントでの同期的 JS 相互運用

このセクションはクライアント側コンポーネントにのみ適用されます。

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

コンポーネントが WebAssembly でのみ実行されることが確実にわかっている場合は、同期 JS 相互運用呼び出しを行うように選択できます。 これにより、非同期呼び出しを行う場合よりもオーバーヘッドがわずかに減少し、レンダリング サイクルが少なくなる可能性があります。これは、結果を待機する間の中間状態が存在しないためです。

クライアント側コンポーネントで JavaScript から .NET への同期呼び出しを行うには、DotNet.invokeMethodAsync ではなく DotNet.invokeMethod を使用します。

同期呼び出しは、次の場合に機能します。

  • コンポーネントは、WebAssembly で実行するためにのみレンダリングされます。
  • 呼び出された関数から同期的に値が返される。 この関数は async メソッドではなく、.NET Task や JavaScript Promise は返されません。

JavaScript の場所

JavaScript の場所に関する記事で説明されている方法のいずれかを使用して、JavaScript (JS) コードを読み込みます。

JS モジュールを使用して JS を読み込む方法については、この記事の「JavaScript モジュールでの JavaScript の分離」セクションを参照してください。

警告

<script> タグは動的に更新できないため、コンポーネントが静的サーバー側レンダリング (静的 SSR) を採用することが保証されている場合にのみ、コンポーネント ファイル (.razor) に <script> タグを配置します。

警告

<script> タグは動的に更新できないため、<script> タグをコンポーネント ファイル (.razor) 内に配置しないでください。

JavaScript モジュールでの JavaScript の分離

Blazor では、標準の JavaScript モジュールに JavaScript (JS) を分離できます (ECMAScript の仕様)。 JavaScript モジュールの読み込みは、他の種類の Web アプリの場合と同じように Blazor で機能し、アプリでモジュールを定義する方法を自由にカスタマイズできます。 JavaScript モジュールの使用方法に関するガイドについては、MDN Web Docs の「JavaScript モジュール」を参照してください。

JS を分離すると、次のようなベネフィットがあります。

  • インポートされる JS によって、グローバル名前空間が汚染されなくなります。
  • ライブラリおよびコンポーネントのコンシューマーは、関連する JS をインポートする必要がありません。

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

import() 演算子を使用した動的インポートは、ASP.NET Core と Blazorでサポートされています。

if ({CONDITION}) import("/additionalModule.js");

前述の例では、プレースホルダー {CONDITION} は、モジュールを読み込む必要があるかどうかを判断するための条件付きチェックを表しています。

ブラウザーの互換性については、「Can I use: JavaScript modules: dynamic import (使用できるかどうか: JavaScript モジュール: dynamic import)」を参照してください。

循環オブジェクト参照の回避

循環参照を含むオブジェクトは、次のいずれに対しても、クライアントでシリアル化することはできません。

  • .NET メソッドの呼び出し。
  • 戻り値の型に循環参照がある場合の、C# からの JavaScript メソッドの呼び出し。

バイト配列のサポート

Blazor では、Base64 へのバイト配列のエンコードおよびデコードを回避する、最適化されたバイト配列 JavaScript (JS) 相互運用がサポートされています。 次の例では、JS 相互運用を使用してバイト配列を .NET に渡します。

sendByteArrayJS 関数を指定します。 この関数は、コンポーネント内のボタンによって静的に呼び出され (これには、invokeMethodAsync の呼び出しのアセンブリ名パラメーターが含まれます)、値を返しません。

CallDotnet8.razor.js:

export function sendByteArray() {
  const data = new Uint8Array([0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
    0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
    0x20, 0x43, 0x61, 0x70, 0x74, 0x61, 0x69, 0x6e, 0x2e, 0x20, 0x4e,
    0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e]);
  DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
    .then(str => {
      alert(str);
    });
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", sendByteArray);
}
<script>
  window.sendByteArray = () => {
    const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
      0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
      0x20,0x43,0x61,0x70,0x74,0x61,0x69,0x6e,0x2e,0x20,0x4e,
      0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
    DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
      .then(str => {
        alert(str);
      });
  };
</script>

Note

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、ASP.NET Core Blazor アプリの JavaScript の位置に関する記事を参照してください。

CallDotnet8.razor:

@page "/call-dotnet-8"
@using System.Text
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 8</PageTitle>

<h1>Call .NET Example 8</h1>

<p>
    <button id="btn">Send Bytes</button>
</p>

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

@code {
    private IJSObjectReference? module;

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

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

    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes) => 
        Task.FromResult(Encoding.UTF8.GetString(receivedBytes, 0, 
            receivedBytes.Length));

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

CallDotnet8.razor:

@page "/call-dotnet-8"
@using System.Text
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 8</PageTitle>

<h1>Call .NET Example 8</h1>

<p>
    <button id="btn">Send Bytes</button>
</p>

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

@code {
    private IJSObjectReference? module;

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

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

    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes) => 
        Task.FromResult(Encoding.UTF8.GetString(receivedBytes, 0, 
            receivedBytes.Length));

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

CallDotNetExample8.razor:

@page "/call-dotnet-example-8"
@using System.Text

<PageTitle>Call .NET 8</PageTitle>

<h1>Call .NET Example 8</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

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

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

CallDotNetExample8.razor:

@page "/call-dotnet-example-8"
@using System.Text

<h1>Call .NET Example 8</h1>

<p>
    <button onclick="sendByteArray()">Send Bytes</button>
</p>

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

@code {
    [JSInvokable]
    public static Task<string> ReceiveByteArray(byte[] receivedBytes)
    {
        return Task.FromResult(
            Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));
    }
}

.NET から JavaScript を呼び出すときのバイト配列の使用については、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。

JavaScript から .NET へのストリーム

Blazor では、JavaScript から .NET に直接データをストリーミングすることがサポートされます。 ストリームは Microsoft.JSInterop.IJSStreamReference インターフェイスを使用して要求されます。

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync では Stream が返され、次のパラメーターが使用されます。

  • maxAllowedSize: JavaScript からの読み取り操作に許可される最大バイト数。指定されていない場合、既定では 512,000 バイトになります。
  • cancellationToken: 読み取りをキャンセルするための CancellationToken

JavaScript の場合:

function streamToDotNet() {
  return new Uint8Array(10000000);
}

C# コードの場合:

var dataReference = 
    await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream = 
    await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

前の例の場合:

  • JS は、挿入された IJSRuntime インスタンスです。 IJSRuntime は Blazor フレームワークによって登録されます。
  • dataReferenceStreamは、現在のユーザーの一時フォルダー パス (GetTempPath) でディスク (file.txt) に書き込まれます。

ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」では、逆の動作である、DotNetStreamReference を使用した .NET から JavaScript へのストリーミングについて説明しています。

ASP.NET Core Blazor ファイルのアップロード」では、Blazor でファイルをアップロードする方法を説明しています。 サーバー側コンポーネントで <textarea> データをストリーミングするフォームの例については、「ASP.NET Core Blazor フォームのトラブルシューティング」を参照してください。

JavaScript [JSImport]/[JSExport] 相互運用

このセクションはクライアント側コンポーネントに適用されます。

IJSRuntime インターフェイスに基づく Blazor の JS 相互運用メカニズムを使用してクライアント側コンポーネントで JavaScript (JS) と対話する代わりに、.NET 7 以降を対象とするアプリで JS[JSImport]/[JSExport] 相互運用 API を使用できます。

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

JavaScript 相互運用オブジェクト参照の破棄

JavaScript (JS) の相互運用に関する記事全体の例では、一般的なオブジェクト破棄パターンが示されています。

JS 相互運用オブジェクト参照は、参照を作成する JS 相互運用呼び出しの側で識別子によってキー付けされたマップとして実装されます。 オブジェクトの破棄が .NET または JS の側から開始されると、Blazor はマップからエントリを削除し、オブジェクトへの強力な参照が他に存在しない限り、オブジェクトをガベージ コレクションできます。

.NET マネージド メモリのリークを回避するため、少なくとも、.NET 側で作成されたオブジェクトを常に破棄します。

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

詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

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

詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

その他のリソース