ブラウザー内で C# のコードを実行できる Blazor ですが、DOM 要素を操作するには JavaScript による処理に頼る必要があります。
この記事では、Blazor における C# と JavaScript との相互運用 (JavaScript Interop) 、また同機能を用いた JavaScript ライブラリの利用方法について解説します。
環境:
- .NET 5
- Blazor WebAssembly
JavaScript Interop の挙動を確認する
Blazor で JavaScript Interop を利用する際は、以下のステップが必要になります。
-
/wwwroot
配下に JavaScript モジュールを配置する - C# から JavaScript モジュールをインポートする
- モジュールが公開している関数を C# から呼び出す
まず、呼び出される JavaScript モジュールを以下の通り定義します。
export function outputLog(obj) {
console.log(typeof obj, obj);
}
C# のソースコードから上記モジュールをインポートし、関数を呼び出します。まず、次の名前空間への参照を追加します。
@inject IJSRuntime JSRuntime
これにより、以下の手順で JavaScript モジュールを参照できます。
var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./js/interop-sample.js"
);
await module.InvokeVoidAsync("outputLog", "my first interop.");
InvokeVoidAsync()
では、実行する関数名を第一引数で、関数へ渡す引数項目をそれ以降の引数で指定します。
これにより引数 obj
へ渡した値の型 string
とその内容 "my first interop"
が、ブラウザーのコンソール上へ出力されます。
string my first interop.
引数として渡せる値は JavaScript 向けに変換が行われます。変換結果の例としては以下の通りです。
await module.InvokeVoidAsync("outputLog", null);
await module.InvokeVoidAsync("outputLog", true);
await module.InvokeVoidAsync("outputLog", false);
// null は undefined へ、bool は boolean へそれぞれ変換される。
// undefined undefined
// boolean true
// boolean false
await module.InvokeVoidAsync("outputLog", 123);
await module.InvokeVoidAsync("outputLog", "456");
await module.InvokeVoidAsync("outputLog", "foo");
// 数値を渡した場合は number へ、文字列を渡した場合は string へ変換される。
// number 123
// string 456
// string foo
// enum MyEnum { Value1 = 7, Value2 = 5, Value3 = 6 }
await module.InvokeVoidAsync("outputLog", MyEnum.Value1);
await module.InvokeVoidAsync("outputLog", MyEnum.Value2);
await module.InvokeVoidAsync("outputLog", MyEnum.Value3);
// 列挙型のメンバーは、対応する整数値へ変換される。
// number 7
// number 5
// number 6
var sampleList = new List<string>() { "aaa", "bbb", "ccc" };
await module.InvokeVoidAsync("outputLog", sampleList);
await module.InvokeVoidAsync("outputLog", sampleList as IEnumerable<string>);
// IEnumerable インターフェイスを備えた型であれば配列へ変換される。
// object (3) ["aaa", "bbb", "ccc"]
// object (3) ["aaa", "bbb", "ccc"]
// class SampleClass
// {
// private string _X = "private field";
// public string X = "public field";
// private string _Y { get; set; } = "private property";
// public string Y { get; set; } = "public property";
// }
var sampleInstance = new SampleClass();
await module.InvokeVoidAsync("outputLog", sampleInstance);
// クラスインスタンスは public プロパティのみを含んだ object へ変換される。
// object {y: "public property"}
await module.InvokeVoidAsync("outputLog", new Dictionary<int, string>() {
{ 1, "value1" },
{ 2, "value2" },
{ 3, "value3" },
});
// Dictionary は object へ変換される。
// object {1: "value1", 2: "value2", 3: "value3"}
await module.InvokeVoidAsync("outputLog", new
{
Item1 = "foo",
Item2 = "hoo",
Item3 = 123
});
// 匿名型も object へ変換される。
// object {item1: "foo", item2: "hoo", item3: 123}
var sampleTuple = (909, 703);
await module.InvokeVoidAsync("outputLog", sampleTuple);
await module.InvokeVoidAsync("outputLog", sampleTuple.ToString());
// Tuple は変換を行えない。
// object {}
// string (909, 703)
var sampleValueTuple = (x: 123, y: "sample");
await module.InvokeVoidAsync("outputLog", sampleValueTuple);
await module.InvokeVoidAsync("outputLog", sampleValueTuple.ToString());
// ValueTuple も同様。
// object {}
// string (123, sample)
この変換処理は以下の注意点があります。
- クラス インスタンスの持つプロパティ名は、先頭を小文字に変換される
-
Tuple
やValueTuple
といった型を持つ値は変換を行えない
Tuple
が利用できないのは意外な気もしますが、JavaScript 側で対応するデータ構造がないことが理由なのかもしれません。
JavaScript ライブラリを利用した処理をモジュールとして定義する
JavaScript Interop による関数の呼び出しと、関数へ渡した値が変換される際の挙動について確認しました。続いて、JavaScript ライブラリを用いて DOM を操作するパターンについて確認します。
例として、Chart.js を利用して画面上へグラフを出力するまでの流れを確認します。
Blazor のテンプレート プロジェクトでは、連日の気温を一覧表示する FetchData.razor
というサンプル コンポーネントが含まれています(気温の数値自体はダミーデータとなっています)。これをもとに、気温の変化を日付順のグラフとして出力させます。
まず、Chart.js のライブラリ本体をダウンロードします。
これを /wwwroot/js/lib
へ配置し、/wwwroot/index.html
にライブラリへの参照を追加します。
<head>
<meta charset="utf-8" />
... 中略
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
+ <script src="js/lib/Chart.min.js"></script>
</head>
/wwwroot/js
配下に、Chart.js を呼び出す以下のモジュールを定義します。
export function createGraph(context, graph) {
new Chart(context, graph)
}
Razor コンポーネント ファイル FetchData.razor
にて IJSRuntime
への参照と、グラフの出力先となる canvas
要素、要素への参照を格納する ElementReference
型のフィールドを追加します。
+ @inject IJSRuntime JSRuntime
<h1>Weather forecast</h1>
... 中略
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
+ <canvas @ref="graphCanvas"></canvas>
+
<table class="table">
<thead>
<tr>
... 中略
}
@code {
private WeatherForecast[] forecasts;
+ private ElementReference graphCanvas;
上記 Razor コンポーネントに対するコードビハインドとして、FetchData.razor.cs
を作成します。
namespace BlazorSamples.Client.Pages
{
public partial class FetchData { }
}
こちらに Chart.js へ渡すパラメータと対応するデータクラスを定義します。
public partial class FetchData
{
class LineData
{
public string Label { get; set; }
public IEnumerable<int> Data { get; set; }
public double Tension { get; set; } = 0.5;
public string BorderColor { get; set; }
}
class LineGraphData
{
public IEnumerable<string> Labels { get; set; }
public IEnumerable<LineData> Datasets { get; set; }
}
class LineGraph
{
public string Type { get; } = "line";
public LineGraphData Data { get; set; }
}
}
次に、これらデータクラスを用いてパラメータを構成する処理を追加します。
protected async void CreateGraph()
{
// WeatherForecastService から得られたデータをグラフ出力用データに変換する
var temparetures = new LineGraphData()
{
// 各データの日付をグラフの軸とする
Labels = forecasts.Select(f => f.Date.ToShortDateString()),
// 摂氏/華氏それぞれの気温情報をグラフの要素として指定する
Datasets = new List<LineData>()
{
new LineData()
{
Label = "Temp. (C)",
Data = forecasts.Select(f => f.TemperatureC),
BorderColor = "coral",
},
new LineData()
{
Label = "Temp. (F)",
Data = forecasts.Select(f => f.TemperatureF),
BorderColor = "lightgreen",
}
}
};
// コンパニオン モジュールへデータを渡す
var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./js/chart-companion.js"
);
await module.InvokeVoidAsync(
"createGraph",
graphCanvas,
new LineGraph() { Data = temparetures }
);
}
上記関数の呼び出しを、ページの初期化処理中に追加します。
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
+ CreateGraph();
}
以上の変更により、画面上へ以下の通りグラフが出力されるようになりました。
ライブラリの関数を呼び出す JavaScript モジュールを用意することで、C# のコードから DOM を操作する手順について確認できたと思います。
ここでは FetchData.razor
コンポーネントへ直接 canvas
要素や各種データクラスを追加しましたが、実際にはグラフを描画する Razor コンポーネントとして定義することが推奨されます。こちらについては別途取り上げたいと思います。
参考:
[ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す | Microsoft Docs]:https://docs.microsoft.com/ja-jp/aspnet/core/blazor/call-javascript-from-dotnet?view=aspnetcore-5.0 "ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す | Microsoft Docs"
- [ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す | Microsoft Docs]