次の方法で共有


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

注意

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

警告

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

重要

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

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

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

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

JS 関数を呼び出す

IJSRuntime は Blazor フレームワークによって登録されます。 .NET から JS を呼び出すには、IJSRuntime 抽象化を挿入し、次のいずれかのメソッドを呼び出します。

JS 関数を呼び出す上記の .NET メソッドについて:

  • 関数の識別子 (String) は、グローバル スコープ (window) を基準とするものです。 window.someScope.someFunction を呼び出す場合の識別子は someScope.someFunction です。 関数は、呼び出す前に登録する必要はありません。
  • Object[] で任意の数の JSON シリアル化可能な引数を JS 関数に渡します。
  • キャンセル トークン (CancellationToken) により、操作を取り消す必要があることを示す通知が伝達されます。
  • TimeSpan は、JS 操作の時間制限を表します。
  • また、戻り値の型 TValue も JSON シリアル化可能である必要があります。 TValue は、返される JSON 型に最適にマップされる .NET 型と一致する必要があります。
  • InvokeAsync メソッドに対しては JS Promise が返されます。 InvokeAsync によって Promise のラップが解除され、Promise によって待機されている値が返されます。

サーバー側アプリの規定値である Blazor アプリでプリレンダリングが有効になっている場合、プリレンダリング中に JS を呼び出すことはできません。 詳しくは、「プリレンダリング」セクションを参照してください。

次の例は、JS ベースのデコーダーである TextDecoder に基づいています。 この例では、開発者コードから既存の JS API に要件をオフロードする C# メソッドから JS 関数を呼び出す方法を示します。 JS 関数は、C# メソッドからバイト配列を受け取り、配列をデコードし、テキストをコンポーネントに返して表示できるようにします。

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

Note

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

次の コンポーネントでは、次を実行します。

  • ボタン (Convert Array) を選択するときに、InvokeAsync を使用して convertArrayJS 関数を呼び出します。
  • JS 関数が呼び出された後、渡された配列が文字列に変換されます。 文字列は、表示できるようにコンポーネントに返されます (text)。

CallJs1.razor:

@page "/call-js-1"
@inject IJSRuntime JS

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray() => 
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}

CallJs1.razor:

@page "/call-js-1"
@inject IJSRuntime JS

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray() => 
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}

CallJsExample1.razor:

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

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

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

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

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

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

CallJsExample1.razor:

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

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

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

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new MarkupString(await JS.InvokeAsync<string>("convertArray", 
            quoteArray));
    }
}

ユーザー ジェスチャに限定された JavaScript API

このセクションは、サーバー側コンポーネントに適用されます。

Fullscreen API (MDN ドキュメント) の使用など、一部のブラウザーの JavaScript (JS) API は、ユーザー ジェスチャのコンテキストでのみ実行できます。 UI イベントの処理は非同期的に実行され、通常はユーザー ジェスチャのコンテキストではなくなるため、これらの API をサーバー側コンポーネントの JS 相互運用メカニズムを通して呼び出すことはできません。 アプリは JavaScript の UI イベントを完全に処理する必要があるため、Blazor の @onclick ディレクティブ属性ではなく、onclick を使用します。

返された値を読み取らずに JavaScript 関数を呼び出す (InvokeVoidAsync)

次の場合に InvokeVoidAsync を使用します。

  • JavaScript (JS) 呼び出しの結果を読み取るために .NET が必要ない。
  • JS 関数で void(0)/void 0 または undefined が返される。

displayTickerAlert1JS 関数を指定します。 関数は InvokeVoidAsync を指定して呼び出され、値を返しません。

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

Note

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

コンポーネント (.razor) の例 (InvokeVoidAsync)

TickerChanged により、次のコンポーネント内の handleTickerChanged1 メソッドが呼び出されます。

CallJs2.razor:

@page "/call-js-2"
@inject IJSRuntime JS

<PageTitle>Call JS 2</PageTitle>

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJs2.razor:

@page "/call-js-2"
@inject IJSRuntime JS

<PageTitle>Call JS 2</PageTitle>

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

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

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

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

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

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

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

CallJsExample2.razor:

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

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

クラス (.cs) の例 (InvokeVoidAsync)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

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

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

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

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChanged により、次のコンポーネント内の handleTickerChanged1 メソッドが呼び出されます。

CallJs3.razor:

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

<PageTitle>Call JS 3</PageTitle>

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized() => jsClass = new(JS);

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJs3.razor:

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

<PageTitle>Call JS 3</PageTitle>

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized() => jsClass = new(JS);

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

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

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

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

CallJsExample3.razor:

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

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses1(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

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

JavaScript 関数を呼び出し、戻り値を読み取る (InvokeAsync)

.NET で JavaScript (JS) 呼び出しの結果を読み取る必要があるときは、InvokeAsync を使用します。

displayTickerAlert2JS 関数を指定します。 次の例では、呼び出し元で表示する文字列が返されます。

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

Note

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

コンポーネント (.razor) の例 (InvokeAsync)

TickerChanged によって handleTickerChanged2 メソッドが呼び出され、返された文字列が次のコンポーネントに表示されます。

CallJs4.razor:

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

<PageTitle>Call JS 4</PageTitle>

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJs4.razor:

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

<PageTitle>Call JS 4</PageTitle>

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}" +
            $"{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
        price = Random.Shared.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

CallJsExample4.razor:

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

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

クラス (.cs) の例 (InvokeAsync)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

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

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
    private readonly IJSRuntime js = js;

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

    // Calling SuppressFinalize(this) prevents derived types that introduce 
    // a finalizer from needing to re-implement IDisposable.
    public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

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

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

    public void Dispose()
    {
    }
}

TickerChanged によって handleTickerChanged2 メソッドが呼び出され、返された文字列が次のコンポーネントに表示されます。

CallJs5.razor:

@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 5</PageTitle>

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized() => jsClass = new(JS);

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJs5.razor:

@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 5</PageTitle>

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized() => jsClass = new(JS);

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}" +
                $"{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

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

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
            price = Random.Shared.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

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

CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

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

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

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

CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses2(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

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

コンテンツの動的生成のシナリオ

BuildRenderTree でコンテンツを動的に生成するには、[Inject] 属性を使用します。

[Inject]
IJSRuntime JS { get; set; }

プリレンダリング

このセクションは、Razor コンポーネントをプリレンダリングするサーバー側アプリに適用されます。 プリレンダリングについては、「ASP.NET Core Razor コンポーネントのプリレンダリングを行う」内で説明しています。

Note

Blazor Web App での対話型ルーティングの内部ナビゲーションでは、サーバーに新しいページ コンテンツを要求することはありません。 そのため、内部ページ要求ではプリレンダリングは行われません。 アプリで対話型ルーティングを採用している場合は、プリレンダリング動作を示すコンポーネントの例でページ全体の再読み込みを実行します。 詳細については、「ASP.NET Core Razor のプリレンダリングとコンポーネント」をご覧ください。

このセクションは、Razor コンポーネントをプリレンダリングするサーバー側アプリおよびホストされている Blazor WebAssembly アプリに適用されます。 プリレンダリングについては、MVC または Razor Pages ASP.NET Core Razor コンポーネントで説明

プリレンダリング中に JavaScript (JS) を呼び出すことはできません。 次の例では、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JS 相互運用を使用する方法を示します。

次の scrollElementIntoView 関数:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

IJSRuntime.InvokeAsync でコンポーネント コードの JS 関数を呼び出すと、以前のライフサイクル メソッドではなく、ElementReferenceOnAfterRenderAsync でのみ使用されます。コンポーネントがレンダリングされるまで HTML DOM 要素が存在しないためです。

StateHasChanged (参照元) は、JS の相互運用呼び出しから取得された新しい状態でコンポーネントの再レンダリングをエンキューするために呼び出されます (詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください)。 StateHasChangedscrollPositionnull である場合にのみ呼び出されるため、無限ループが作成されることはありません。

PrerenderedInterop.razor:

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

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

前の例では、クライアントがグローバル関数で汚染されます。 実稼働アプリでのより適切なアプローチについては、「JavaScript モジュール内の JavaScript 分離」を参照してください。

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

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

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

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

クライアント側コンポーネントで .NET から JavaScript への同期呼び出しを行うには、IJSRuntimeIJSInProcessRuntime にキャストして JS 相互運用呼び出しを行います。

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

ASP.NET Core 5.0 以降のクライアント側コンポーネントの IJSObjectReference で作業する場合は、代わりに IJSInProcessObjectReference を同期的に使用できます。 IJSInProcessObjectReferenceIAsyncDisposable/IDisposable を実装しており、次の例に示すように、メモリ リークを防ぐためにガベージ コレクションに破棄する必要があります。

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

前の例では、Blazor WebAssembly アプリに失われるBlazorSignalR回線がないため、モジュールの破棄中にJSDisconnectedExceptionがトラップされません。 詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

JavaScript の場所

JavaScript の場所に関する記事で説明されている方法のいずれかを使用して、JavaScript (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 をインポートする必要がありません。

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

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

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

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

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

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

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

  • サーバー側コンポーネントは、 IAsyncDisposableを実装します。
  • moduleは、JS モジュールのIJSObjectReferenceです。
  • JSDisconnectedException がキャッチされ、ログはされません。
  • 必要に応じて、任意のログ レベルで catch ステートメントにカスタム情報をログできます。 次の例では、カスタム情報はログに記録されません。 このコードは、コンポーネントの破棄中に回線が切断されるタイミングや場所を開発者が気にしないことを前提としています。
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

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

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

次のJSモジュールは、browser ウィンドウ プロンプトを表示するためのJS関数エクスポートします。 次の JS コードを外部の JS ファイルに配置します。

wwwroot/scripts.js:

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

前の JS モジュールを wwwroot フォルダー内の静的 Web アセットとしてアプリまたはクラス ライブラリに追加し、IJSRuntime インスタンスで InvokeAsync を呼び出すことにより、そのモジュールを .NET コードにインポートします。

IJSRuntime により、モジュールが IJSObjectReference としてインポートされます。これは、.NET コードから JS オブジェクトへの参照を表します。 モジュールからエクスポートされた JS 関数を呼び出すには、IJSObjectReference を使用します。

CallJs6.razor:

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

<PageTitle>Call JS 6</PageTitle>

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt() => result = await Prompt("Provide text");

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJs6.razor:

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

<PageTitle>Call JS 6</PageTitle>

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt() => result = await Prompt("Provide text");

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

CallJsExample6.razor:

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

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference module;
    private string result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

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

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

前の例の場合:

  • 慣例により、import 識別子は、JS モジュールをインポートするために特別に使用される特殊な識別子です。
  • 安定した静的な Web アセット パス ./{SCRIPT PATH AND FILE NAME (.js)} を使用して、モジュールの外部 JS ファイルを指定します。
    • JS ファイルへの正しい静的アセット パスを作成するためには、現在のディレクトリ (./) に対するパス セグメントが必要です。
    • {SCRIPT PATH AND FILE NAME (.js)} プレースホルダーは、wwwroot の下のパスとファイル名です。
  • IAsyncDisposable.DisposeAsyncガベージ コレクション用の IJSObjectReference を破棄します。

モジュールを動的にインポートするにはネットワーク要求が必要であるため、これは InvokeAsync を呼び出すことによってのみ、非同期的に実現できます。

IJSInProcessObjectReference は、クライアント側コンポーネントで関数を同期的に呼び出すことができる JS オブジェクトへの参照を表します。 詳細については、「クライアント側コンポーネントでの同期 JS 相互運用」セクションを参照してください。

Note

Razor クラス ライブラリによって外部の JS ファイルが提供される場合は、安定した静的 Web アセット パス ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)} を使用してモジュールの JS ファイルを指定します。

  • JS ファイルへの正しい静的アセット パスを作成するためには、現在のディレクトリ (./) に対するパス セグメントが必要です。
  • {PACKAGE ID} プレースホルダーは、ライブラリのパッケージ ID です。 プロジェクト ファイルで <PackageId> が指定されていない場合、パッケージ ID の既定値はプロジェクトのアセンブリ名になります。 次の例では、ライブラリのアセンブリ名は ComponentLibrary であり、ライブラリのプロジェクト ファイルで <PackageId> が指定されていません。
  • {SCRIPT PATH AND FILE NAME (.js)} プレースホルダーは、wwwroot の下のパスとファイル名です。 次の例では、外部 JS ファイル (script.js) はクラス ライブラリの wwwroot フォルダーに配置されます。
  • module は、コンポーネント クラス (private IJSObjectReference? module;) のプライベートで null 許容の IJSObjectReference です。
module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

詳細については、「ASP.NET Core Razor コンポーネントを Razor クラス ライブラリから使用する」を参照してください。

Blazor のドキュメント全体を通して、モジュール ファイルには新しい .mjs ファイル拡張子 (RFC 9239) ではなく .js ファイル拡張子を例に使っています。 Mozilla Foundation のドキュメントで .js ファイル拡張子を使い続けているのと同じ理由で、このドキュメントでも .js ファイル拡張子を使い続けています。 詳細については、「余談 - .mjs と js (MDN ドキュメント)」を参照してください。

要素への参照をキャプチャする

一部の JavaScript (JS) 相互運用シナリオでは、HTML 要素への参照が必要です。 たとえば、UI ライブラリで初期化のための要素参照が必要な場合、clickplay などの要素でコマンドのような API の呼び出し必要になる可能性があります。

次の方法を使用して、コンポーネント内の HTML 要素への参照をキャプチャします。

  • @ref 属性を HTML 要素に追加します。
  • 名前が @ref 属性の値に一致する ElementReference 型のフィールドを定義します。

次の例は、username <input> 要素への参照をキャプチャする方法を示しています。

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

@code {
    private ElementReference username;
}

警告

Blazor とやりとりしない空の要素のコンテンツを変化させるには、要素参照のみを使用します。 このシナリオは、サードパーティの API から要素にコンテンツが提供される場合に便利です。 Blazor は要素とやりとりしないため、Blazor の要素表現と DOM との間に競合が発生する可能性がありません。

次の例では、Blazor が DOM と対話してこの要素のリスト項目 (<li>) を Todos オブジェクトから設定するため、JS 相互運用により MyList を使って順序なしリスト (ul) のコンテンツを変化させるのは "危険" です。

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

MyList 要素の参照を使って単純に DOM コンテンツを読み取ったり、イベントをトリガーしたりすることがサポートされています。

JS 相互運用により要素 MyList の "コンテンツが変更され"、Blazor でその要素に差分を適用しようとした場合、差分は DOM と一致しません。 MyList 要素の参照を使った JS 相互運用によるリストのコンテンツの変更は "サポートされていません"。

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

ElementReference は JS 相互運用経由で JS コードに渡されます。 JS コードが HTMLElement インスタンスを受け取り、通常の DOM API で使用できます。 たとえば、次のコードでは、要素にマウス クリックを送信できるようにする .NET 拡張メソッド (TriggerClickEvent) を定義しています。

JS 関数 clickElement により、渡された HTML 要素 (element) で click イベントが作成されます。

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

値を返さない JS 関数を呼び出すには、JSRuntimeExtensions.InvokeVoidAsync を使用します。 次のコードは、キャプチャされた ElementReference で前述の JS 関数を呼び出し、クライアント側の click イベントをトリガーします。

@inject IJSRuntime JS

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

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

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

拡張メソッドを使用するには、IJSRuntime インスタンスを受け取る静的拡張メソッドを作成します。

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

clickElement メソッドは、オブジェクトで直接呼び出されます。 次の例では、TriggerClickEvent メソッドが JsInteropClasses 名前空間から使用できることを前提としています。

@inject IJSRuntime JS
@using JsInteropClasses

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

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

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

重要

exampleButton 変数は、コンポーネントがレンダリングされた後にのみ設定されます。 未設定の ElementReference が JS コードに渡された場合、JS コードは null の値を受け取ります。 コンポーネントのレンダリングが完了した後で要素の参照を操作するには、OnAfterRenderAsync または OnAfterRender コンポーネント ライフサイクル メソッドを使用します。

ジェネリック型を操作して値を返す場合は、ValueTask<TResult> を使用します。

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

{JAVASCRIPT FUNCTION} プレースホルダーは、JS 関数識別子です。

GenericMethod は、型を持つオブジェクトで直接呼び出されます。 次の例では、GenericMethodJsInteropClasses 名前空間から使用できることを前提としています。

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}
@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

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

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}
@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

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

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

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

コンポーネント間で要素を参照する

ElementReference は次の理由からコンポーネントで渡すことができません。

親コンポーネントが要素参照を他のコンポーネントで使用できるようにするために、親コンポーネントは次のことを実行できます。

  • 子コンポーネントがコールバックを登録できるようにします。
  • 渡された要素参照を使用して、登録されたコールバックをOnAfterRender イベント中に呼び出します。 間接的には、この方法により、子コンポーネントが親の要素参照とやりとりできるようになります。
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Note

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

CallJs7.razor (親コンポーネント):

@page "/call-js-7"

<PageTitle>Call JS 7</PageTitle>

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJs7.razor (親コンポーネント):

@page "/call-js-7"

<PageTitle>Call JS 7</PageTitle>

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (親コンポーネント):

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

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (親コンポーネント):

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

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (親コンポーネント):

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

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJsExample7.razor (親コンポーネント):

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

<h1>Call JS Example 7</h1>

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

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

CallJs7.razor.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJs7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private readonly List<IObserver<ElementReference>> subscriptions = [];
    private ElementReference title;

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

        foreach (var subscription in subscriptions)
        {
            subscription.OnNext(title);
        }
    }

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

CallJs7.razor.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJs7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private readonly List<IObserver<ElementReference>> subscriptions = [];
    private ElementReference title;

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

        foreach (var subscription in subscriptions)
        {
            subscription.OnNext(title);
        }
    }

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages;

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

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

        foreach (var subscription in subscriptions)
        {
            subscription.OnNext(title);
        }
    }

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

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

CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages;

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

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

        foreach (var subscription in subscriptions)
        {
            subscription.OnNext(title);
        }
    }

    public void Dispose()
    {
        disposing = true;

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

        subscriptions.Clear();
    }

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

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

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

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

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

CallJsExample7.razor.cs:

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

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

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

            foreach (var subscription in subscriptions)
            {
                subscription.OnNext(title);
            }
        }

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

CallJsExample7.razor.cs:

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

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

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

            foreach (var subscription in subscriptions)
            {
                subscription.OnNext(title);
            }
        }

        public void Dispose()
        {
            disposing = true;

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

            subscriptions.Clear();
        }

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

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

前の例で、アプリの名前空間は BlazorSample です。 コードをローカル環境でテストする場合は、名前空間を更新します。

SurveyPrompt.razor (子コンポーネント):

<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}

SurveyPrompt.razor.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Components;

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

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = Parent?.Subscribe(this);
    }

    public void OnCompleted() => subscription = null;

    public void OnError(Exception error) => subscription = null;

    public void OnNext(ElementReference value) =>
        _ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));

    public void Dispose()
    {
        subscription?.Dispose();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Components;

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

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = Parent?.Subscribe(this);
    }

    public void OnCompleted() => subscription = null;

    public void OnError(Exception error) => subscription = null;

    public void OnNext(ElementReference value) =>
        _ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));

    public void Dispose()
    {
        subscription?.Dispose();

        // The following prevents derived types that introduce a
        // finalizer from needing to re-implement IDisposable.
        GC.SuppressFinalize(this);
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared;

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

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = 
            Parent is not null ? Parent.Subscribe(this) : null;
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void OnNext(ElementReference value)
    {
        JS.InvokeAsync<object>(
            "setElementClass", new object[] { value, "red" });
    }

    public void Dispose()
    {
        subscription?.Dispose();
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Shared;

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

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    [Inject]
    public IJSRuntime? JS {get; set;}

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = 
            Parent is not null ? Parent.Subscribe(this) : null;
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void OnNext(ElementReference value)
    {
        JS.InvokeAsync<object>(
            "setElementClass", new object[] { value, "red" });
    }

    public void Dispose()
    {
        subscription?.Dispose();
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

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

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        [Inject]
        public IJSRuntime JS {get; set;}

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            subscription?.Dispose();
        }
    }
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

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

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        [Inject]
        public IJSRuntime JS {get; set;}

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

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

前の例で、アプリの名前空間は Shared フォルダー内に共有コンポーネントがある BlazorSample です。 コードをローカル環境でテストする場合は、名前空間を更新します。

JavaScript 相互運用の呼び出しを強化する

このセクションは対話型のサーバー コンポーネントにのみ適用されますが、クライアント側コンポーネントでも条件が整えば、JS 相互運用タイムアウトを設定できます。

サーバー対話型のサーバー側アプリでの JavaScript (JS) 相互運用は、ネットワーク エラーにより失敗するおそれがあるため、信頼性の低いものとして扱う必要があります。 Blazor アプリでは、JS 相互運用の呼び出しに対して 1 分間のタイムアウトが使用されます。 アプリでより積極的なタイムアウトが許容される場合は、次のいずれかの方法を使用してタイムアウトを設定します。

Program.csCircuitOptions.JSInteropDefaultCallTimeout を使用してグローバル タイムアウトを設定します。

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

CircuitOptions.JSInteropDefaultCallTimeout を使用して Startup.csStartup.ConfigureServices メソッドでグローバル タイムアウトを設定します。

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

{TIMEOUT} プレースホルダーは、TimeSpanです (たとえば、TimeSpan.FromSeconds(80))。

コンポーネントのコードで呼び出しごとにタイムアウトを設定します。 この方法で指定したタイムアウトは、JSInteropDefaultCallTimeout によって設定されたグローバル タイムアウトを上書きします。

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

前の例の場合:

  • {TIMEOUT} プレースホルダーは、TimeSpanです (たとえば、TimeSpan.FromSeconds(80))。
  • {ID} プレースホルダーは、呼び出す関数の識別子です。 たとえば、値 someScope.someFunction を指定すると、関数 window.someScope.someFunction が呼び出されます。

JS 相互運用のエラーの一般的な原因は、サーバー側コンポーネントでのネットワーク障害ですが、クライアント側コンポーネント用の JS 相互運用の呼び出しに対し、呼び出しごとのタイムアウトを設定できます。 クライアント側コンポーネントにBlazorSignalR回線は存在しませんが、JS相互運用呼び出しは、他の理由で失敗する可能性があります。

リソース枯渇の詳細については、対話型サーバー側の ASP.NET Core Blazor レンダリングの脅威軽減策に関するガイダンスに関する記事を参照してください。

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

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

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

UI をレンダリングする JavaScript ライブラリ

ブラウザー DOM 内に表示可能なユーザー インターフェイス要素を生成する JavaScript (JS) ライブラリを使用することが必要になる場合があります。 Blazor の差分システムは、DOM 要素のツリーに対する制御に依存しており、外部コードによって DOM ツリーが変更されて、差分を適用するためのメカニズムが無効になるとエラーが発生するため、一見すると、これは困難に思えるかもしれません。 これは、Blazor に固有の制限ではありません。 差分ベースの UI フレームワークでは同じ課題が発生します。

幸い、外部で生成された UI を Razor コンポーネントの UI に確実に埋め込むのは簡単です。 推奨される方法は、コンポーネントのコード (.razor ファイル) で空の要素を生成することです。 Blazor の差分システムに関する限り、要素は常に空であるため、レンダラーによって要素は再帰されず、代わりにその内容はそのままの状態になります。 これにより、外部で管理されている任意の内容を要素に設定しても安全になります。

次の例はこの概念を示したものです。 if ステートメント内で firstRendertrue のとき、JS 相互運用を使用して Blazor の外部の unmanagedElement と対話します。 たとえば、外部の JS ライブラリを呼び出して要素を設定します。 このコンポーネントが削除されるまで、要素の内容が Blazor によって操作されることはありません。 コンポーネントが削除されると、コンポーネントの DOM サブツリー全体も削除されます。

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

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

@code {
    private ElementReference unmanagedElement;

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

オープンソースの Mapbox API を使用して対話型マップをレンダリングする次のような例について考えてみます。

次の JS モジュールは、アプリに配置されるか、Razor クラス ライブラリから使用できます。

メモ

Mapbox マップを作成するには、Mapbox サインインからアクセス トークンを取得し、次のコードの {ACCESS TOKEN} の場所でそれを指定します。

wwwroot/mapComponent.js:

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

mapboxgl.accessToken = '{ACCESS TOKEN}';

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

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

正しいスタイルを生成するには、ホストの HTML ページに次のスタイルシート タグを追加します。

次の <link> 要素を <head> 要素マークアップに追加します (<head> コンテンツの場所)。

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

CallJs8.razor:

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

<PageTitle>Call JS 8</PageTitle>

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJs8.razor:

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

<PageTitle>Call JS 8</PageTitle>

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

CallJsExample8.razor:

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

<h1>Call JS Example 8</h1>

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

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

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

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

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

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

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

前の例では、対話型のマップ UI が生成されます。 ユーザーは:

  • ドラッグしてスクロールまたはズームできます。
  • ボタンを選択して、あらかじめ定義されている場所に移動します。

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

前の例の場合:

  • @ref="mapElement" が含まれる <div> は、Blazor に関する限り空のままになります。 mapbox-gl.js スクリプトにより、要素を安全に設定し、その内容を変更できます。 この手法は、UI をレンダリングする任意の JS ライブラリで使用します。 ページの他の部分に手を伸ばして変更しようとしない限り、サードパーティの JS SPA フレームワークのコンポーネントを Razor コンポーネントの内部に埋め込むことができます。 Blazor によって空と見なされない要素を、外部の JS コードで変更することは、安全ではありません
  • このアプローチを使用する場合は、Blazor によって DOM 要素が保持または破棄される方法に関する規則に留意してください。 DOM 要素が可能な限り保持されるため、コンポーネントにより安全にボタン クリック イベントが処理され、既存のマップ インスタンスが更新されます。 @foreach ループの内側からマップ要素のリストをレンダリングしていた場合は、@key を使用して、コンポーネントのインスタンスを確実に保持する必要があります。 そうしないと、リスト データを変更した場合、コンポーネントのインスタンスによって前のインスタンスの状態が望ましくない状態で保持される可能性があります。 詳細については、@key ディレクティブ属性を使って要素、コンポーネント、モデル オブジェクト間のリレーションシップを保持する方法に関する記事を参照してください。
  • この例では、JS のロジックと依存関係が JavaScript モジュール内にカプセル化されており、その import 識別子を使用してモジュールが動的に読み込まれます。 詳細については、「JavaScript モジュールでの JavaScript の分離」を参照してください。

バイト配列のサポート

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

receiveByteArrayJS 関数を指定します。 関数は InvokeVoidAsync を指定して呼び出され、値を返しません。

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

Note

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

CallJs9.razor:

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

<h1>Call JS Example 9</h1>

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

<p>
    @result
</p>

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

@code {
    private string? result;

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

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

CallJsExample9.razor:

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

<h1>Call JS Example 9</h1>

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

<p>
    @result
</p>

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

@code {
    private string? result;

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

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

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

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

Blazor では、.NET から JavaScript (JS) に直接データをストリーミングすることがサポートされています。 ストリームは、DotNetStreamReference を使用して作成されます。

DotNetStreamReference は .NET ストリームを表し、次のパラメーターが使用されます。

  • stream: JS に送信されるストリーム。
  • leaveOpen: 転送後にストリームを開いたままにするかどうかを指定します。 値が指定されていない場合、leaveOpen は既定で false になります。

JS では、配列バッファーまたは読み取り可能なストリームを使用してデータを受信します。

  • ArrayBuffer を使用する場合:

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

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

C# コードの場合:

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

前の例の場合:

  • {STREAM} プレースホルダーは、JS に送信される Stream を表します。
  • JS は、挿入された IJSRuntime インスタンスです。

通常、DotNetStreamReference インスタンスを破棄する必要はありません。 leaveOpen が既定値の false に設定されている場合、基になる Stream は、JS への送信後に自動的に破棄されます。

leaveOpentrue の場合、DotNetStreamReference を破棄しても、基になる Stream は破棄されません。 基になる Stream を破棄するタイミングはアプリのコードで定めます。 基になる Stream を破棄する方法を決定する際には、次の点を考慮します。

  • JS への送信中に Stream を破棄すると、アプリケーション エラーと見なされ、ハンドルされない例外が発生する可能性があります。
  • Stream の送信は、ストリームが実際に JS ロジックで使用されているかどうかに関わりなく、JS 相互運用呼び出しに DotNetStreamReference が引数として渡されるとすぐに開始します。

これらの特性を考慮して、基になる Stream は、JS (arrayBuffer 解決または stream 解決によって返される Promise) による使用が完了した後にのみ破棄することをお勧めします。 つまり、DotNetStreamReference は、JS ロジックで無条件に使用される場合にのみ JS に渡されるべきだということになります。

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

ASP.NET Core Blazor でのファイルのダウンロード」では、Blazor でファイルをダウンロードする方法について説明しています。

JavaScript の例外をキャッチする

JS の例外をキャッチするには、JS の相互運用を try-catch ブロックにラップし、JSException をキャッチします。

次の例では、nonFunctionJS 関数は存在しません。 関数が見つからないとき、次のエラーを示す Message を含む JSException がトラップされます。

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

CallJs11.razor:

@page "/call-js-11"
@inject IJSRuntime JS

<PageTitle>Call JS 11</PageTitle>

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJs11.razor:

@page "/call-js-11"
@inject IJSRuntime JS

<PageTitle>Call JS 11</PageTitle>

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

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

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

CallJsExample11.razor:

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

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

実行時間の長い JavaScript 関数を中止する

C# コードから実行時間の長い JavaScript 関数を中止するには、コンポーネントの CancellationTokenSource と共に JSAbortController を使います。

次の JSHelpers クラスには、シミュレートされた実行時間の長い関数 longRunningFn が含まれ、AbortController.abort が呼び出されたことを AbortController.signal が示すまで継続的にカウントします。 sleep 関数は、実行時間の長い関数の低速な実行をシミュレートするためのデモンストレーション用であり、運用コードには存在しません。 コンポーネントによって stopFn が呼び出されると、AbortSignal.aborted に関する while ループの条件チェックを介して、longRunningFn は中止するようシグナルを受け取ります。

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</script>

Note

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

次の コンポーネントでは、次を実行します。

CallJs12.razor:

@page "/call-js-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

CallJsExample12.razor:

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

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

ブラウザーの開発者ツール コンソールでは、Start Task ボタンを選択した後の実行時間の長い JS 関数の実行と、Cancel Task ボタンを選択した後に関数が中止されるタイミングが示されます。

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

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

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

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

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

マーシャリングされていない JavaScript 相互運用

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

IJSUnmarshalledRuntime インターフェイスを使用したマーシャリング解除された相互運用は廃止されており、JavaScript [JSImport]/[JSExport] 相互運用機能に置き換える必要があります。

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

マーシャリングされていない JavaScript 相互運用

.NET オブジェクトが JavaScript (JS) 相互運用のためにシリアル化され、次のいずれかに該当する場合、Blazor WebAssembly コンポーネントのパフォーマンスが低下するおそれがあります。

  • 大量の .NET オブジェクトが短時間でシリアル化される。 たとえば、JS 相互運用の呼び出しが、マウス ホイールの回転など、入力デバイスの動きに基づいて行われるときは、パフォーマンスが低下する場合があります。
  • 大きな .NET オブジェクトまたは多数の .NET オブジェクトを JS 相互運用のためにシリアル化する必要がある。 たとえば、JS 相互運用の呼び出しで数十個のファイルをシリアル化する必要があるときは、パフォーマンスが低下する場合があります。

IJSUnmarshalledObjectReference は、.NET データをシリアル化するオーバーヘッドなしで関数を呼び出すことができる JS オブジェクトへの参照を表します。

次に例を示します。

  • 文字列と整数を含む struct は、シリアル化されずに JS に渡されます。
  • JS 関数によってデータが処理され、ブール値または文字列が呼び出し元に返されます。
  • JS の文字列を .NET の string オブジェクトに直接変換することはできません。 unmarshalledFunctionReturnString 関数によって BINDING.js_string_to_mono_string が呼び出され、JS の文字列の変換が管理されます。

メモ

次の例は、JS に渡された struct によってコンポーネントのパフォーマンスは低下しないので、このシナリオの一般的な使用例ではありません。 この例では、シリアル化されていない .NET データを渡すための概念を示すためだけに、小さなオブジェクトを使用しています。

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

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

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

Note

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

警告

js_string_to_mono_string 関数の名前、動作、および存在は、.NET の今後のリリースで変更される可能性があります。 次に例を示します。

  • 関数の名前が変更される可能性があります。
  • フレームワークによる文字列の自動変換を優先して、関数自体が削除される可能性があります。

CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string? callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

IJSUnmarshalledObjectReference インスタンスが C# コードで破棄されない場合は、JS で破棄することができます。 次の dispose 関数を使用すると、JS から呼び出されたときにオブジェクト参照が破棄されます。

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

    ...
  };
}

配列型は、js_typed_array_to_array を使用して JS オブジェクトから .NET オブジェクトに変換できますが、JS の配列は型指定された配列である必要があります。 JS の配列は、C# コードで .NET オブジェクト配列 (object[]) として読み取ることができます。

文字列配列などの他のデータ型は変換できますが、新しい Mono 配列オブジェクト (mono_obj_array_new) を作成し、その値 (mono_obj_array_set) を設定する必要があります。

警告

js_typed_array_to_arraymono_obj_array_newmono_obj_array_set など、Blazor フレームワークによって提供される JS 関数は、.NET の今後のリリースで、名前の変更、動作の変更、または削除の対象となる場合があります。

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

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

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

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

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

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

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

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

その他の技術情報