Blazor WebAssemblyをただC#実行プラットフォームとして使って既存のReactのWebアプリを拡張した話


Blazor Advent Calendar 2020 1日目の記事です

おことわり

特定技術の1日目の記事なんだし、Blazorとはみたいな前提の共有するよね!?と思った方すみません…
Blazor WebAssemblyがGAされて半年経過していますし、いろんな解説記事が溢れているはずと信じてその辺りは割愛します。

公式ドキュメントはこちら
ASP.NET Core Blazor の概要 | Microsoft Docs

…さて、ドキュメント読みましたか?
Blazor(の中のBlazor WebAssembly)についてわかりましたか?
概要の所を一部抜粋すると

  • Blazor WebAssembly は、.NET を使って対話型のクライアント側 Web アプリを構築するためのシングルページ アプリ (SPA) フレームワークです。
  • WebAssembly (略称 wasm) によって、Web ブラウザー内で .NET コードを実行することが可能になります。

「.NETでSPAクライアントが書けて、ブラウザ上で.NETコードを実行できるいい感じのフレームワーク」らしいです、すごいフレームワークですね。
そんなすごいBlazor WebAssemblyを使ってみたら良かった話をします。

今回の話のテーマとなるアプリケーション

Repository: https://github.com/yamachu/cognitive-cv-visualizer
WebSite: https://cognitive-cv-visualizer.yamachu.dev/

React.jsでUIが書かれていて、C#でAPIが書かれている素朴なアプリケーションが今回のテーマです。

ざっくりとこのアプリケーションは何をするかと言うと、ユーザがこのページにドラッグ&ドロップした画像に対してOCRをかけて、その領域を描画します。
動作こそしますが、このアプリケーション(あるいは作り)には問題点が一つありました。

このアプリケーションのOCRの部分はAzure Cognitive ServicesのComputer Visionに含まれるものを使用しています。
このOCRのAPIを使用するためにはCognitive Servicesのサブスクリプションが必要ですが、そのサブスクリプションのキーやAPIのエントリポイントの情報はユーザに入力してもらって、自分の実装したAPIに送信し、APIからCognitive Servicesの機能を叩くみたいことをしています。

送られてきたパラメータをdumpしたりログに残していない限りそのキーが漏れることはないでしょうが、自分で実装したAPI側で何らかの例外を起こしてRequestParameterなどもトレースに表れてしまった場合見てはいけないユーザのCredentialsを自分が見れてしまう状態になってしまいます。

問題解決に向けて

上記の問題解決に向けて実装時に考えたことが書いてあったり実装を行ったPRがこちら
https://github.com/yamachu/cognitive-cv-visualizer/pull/7

Uncontrollableな外部APIと連携している自分で実装したAPIが一番のネックとなっています。
なのでこの問題を解決するにはこの自分の実装しているAPIから引き剥がすのが一番の近道と言えます。
それを達成するためにAPI側で行っていることをJSでリライトする手もありましたが、今回はBlazor WebAssemblyを採用しました。

理由としては

  • APIがC#で書かれていたので、Interfaceを調整すればそのまま移植できそうだった
  • APIに投げていたパラメータが文字列の集合で容易に表現可能だった

という点が挙げられます。

公式ドキュメントには『Blazor WebAssembly は、.NET を使って対話型のクライアント側 Web アプリを構築するためのシングルページ アプリ (SPA) フレームワークです』と書かれていて既に仮想DOMをJSで管理していた場合はどうなるんだろうという不安がありましたが、Blazor WebAssemblyも静的なHTMLの特定のDOMツリーの下にマウントするという形でDOMを管理しているため相互に参照し合うことがなく、また別のツリーの出来事なので問題はありませんでした。

それでは実際に行った移行プロセスを紹介します。

  1. [C#] dotnetコマンドでBlazor WebAssemblyのテンプレートを作成
  2. [C#] APIとして提供していた機能の切り出し
  3. [C#] 当該機能を呼び出すJSInvokableアノテーションのついたロジックをDOMツリーにマウントするコンポーネントに実装
  4. [JS] JSInvokableアノテーションを呼び出すメソッドを実装
  5. [JS] APIとBlazor WebAssembly側が提供しているメソッドを呼び出す箇所の差し替え
  6. [HTML] Blazor WebAssemblyを実行するためのエントリポイントを追加

の大まかに6ステップです。

1はドキュメント通りで、2はアプリケーションに依ることなので省略します。
3、4は公式ドキュメントのASP.NET Core Blazor で JavaScript 関数から .NET メソッドを呼び出すに沿って行いました。

コードを例に挙げると、
C#(Blazor)側でこんな感じのコンポーネントを実装して(https://github.com/yamachu/cognitive-cv-visualizer/pull/7/commits/c4b0d32f564e00b352ca014be0e240306d5066fa)

@using System.Net.Http
@using CVVisualizer.Core

@code {
    private static HttpClient httpClient = new HttpClient();

    [JSInvokable]
    public static Task<string> RunOCR(string endpoint, string subscriptionKey, string imageBase64)
    {
        var image = Convert.FromBase64String(imageBase64);
        return VisionOCRService.AnalyzeAsync(httpClient, endpoint, subscriptionKey, image);
    }
}

JS側で(https://github.com/yamachu/cognitive-cv-visualizer/pull/7/commits/a50833857959438219598256c9fdd1d08926328a)

return window.DotNet.invokeMethodAsync(
          "CVVisualizer.Blazor",
          "RunOCR",
          formEndpoint,
          formSubscriptionKey,
          base64File
        );

こんな感じのコードを書くだけです。
C#側のBlazor WebAssemblyは『シングルページ アプリ (SPA) フレームワークです』と言われているのに一切タグを吐き出さない構成となっています。

これこそがタイトルにした「Blazor WebAssemblyをただC#実行プラットフォームとして使って」という意味です(タイトル回収)。

5、6も実装に依るのでこの記事ではスキップします。

おわりに

Blazor WebAssemblyを既存のWebアプリケーションに付け加えて、不安のあるAPIを一つ消し去ることが出来ました。
この様に全てBlazorのライフサイクルに乗せたりUIやStateの管理を行わなければいけないということはなく、ただ.NETのコードを実行する環境だけとして使うことも可能なのです。
例えばドメインロジックなどを移植してフロント側を固くしたりすることも出来そうですね。
様々な可能性を秘めたBlazor、多くの人が使って更に知見が増えていけばいいなと思います。

明日は @jsakamoto さんのデザインコンポーネントのお話です。

フルBlazorで作ると苦労しがちなデザインなので、デザインフレームワークの話は気になりますね、お楽しみに。