19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

BlazorAdvent Calendar 2020

Day 12

Blazor から JavaScript ライブラリを利用する

Last updated at Posted at 2020-12-12

ブラウザー内で C# のコードを実行できる Blazor ですが、DOM 要素を操作するには JavaScript による処理に頼る必要があります。

この記事では、Blazor における C# と JavaScript との相互運用 (JavaScript Interop) 、また同機能を用いた JavaScript ライブラリの利用方法について解説します。

環境:

  • .NET 5
  • Blazor WebAssembly

JavaScript Interop の挙動を確認する

Blazor で JavaScript Interop を利用する際は、以下のステップが必要になります。

  1. /wwwroot 配下に JavaScript モジュールを配置する
  2. C# から JavaScript モジュールをインポートする
  3. モジュールが公開している関数を C# から呼び出す

まず、呼び出される JavaScript モジュールを以下の通り定義します。

wwwroot/js/interop-sample.js
export function outputLog(obj) {
   console.log(typeof obj, obj);
}

C# のソースコードから上記モジュールをインポートし、関数を呼び出します。まず、次の名前空間への参照を追加します。

InteropSample.razor
@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)

この変換処理は以下の注意点があります。

  • クラス インスタンスの持つプロパティ名は、先頭を小文字に変換される
  • TupleValueTuple といった型を持つ値は変換を行えない

Tuple が利用できないのは意外な気もしますが、JavaScript 側で対応するデータ構造がないことが理由なのかもしれません。

JavaScript ライブラリを利用した処理をモジュールとして定義する

JavaScript Interop による関数の呼び出しと、関数へ渡した値が変換される際の挙動について確認しました。続いて、JavaScript ライブラリを用いて DOM を操作するパターンについて確認します。

例として、Chart.js を利用して画面上へグラフを出力するまでの流れを確認します。

Blazor のテンプレート プロジェクトでは、連日の気温を一覧表示する FetchData.razor というサンプル コンポーネントが含まれています(気温の数値自体はダミーデータとなっています)。これをもとに、気温の変化を日付順のグラフとして出力させます。

image.png

まず、Chart.js のライブラリ本体をダウンロードします。

これを /wwwroot/js/lib へ配置し、/wwwroot/index.html にライブラリへの参照を追加します。

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 を呼び出す以下のモジュールを定義します。

chart-companion.js
export function createGraph(context, graph) {
   new Chart(context, graph)
}

Razor コンポーネント ファイル FetchData.razor にて IJSRuntime への参照と、グラフの出力先となる canvas 要素、要素への参照を格納する ElementReference 型のフィールドを追加します。

Pages/FetchData.razor
+	@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 を作成します。

Pages/FetchData.razor.cs
namespace BlazorSamples.Client.Pages
{
   public partial class FetchData { }
}

こちらに Chart.js へ渡すパラメータと対応するデータクラスを定義します。

Pages/FetchData.razor.cs
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; }
   }
}

次に、これらデータクラスを用いてパラメータを構成する処理を追加します。

Pages/FetchData.razor.cs
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 }
   );
}

上記関数の呼び出しを、ページの初期化処理中に追加します。

Pages/FetchData.razor
	protected override async Task OnInitializedAsync()
	{
	   forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
+	   CreateGraph();
	}

以上の変更により、画面上へ以下の通りグラフが出力されるようになりました。

image.png

ライブラリの関数を呼び出す 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]
19
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?