WebView2 C#とJavaScriptの連携

昨日の今日ではあるんですが、WebView2を使ってJavaScriptとC#を連携させる方法がわかったので、記事に残します。
（筆者の個人的な事情なんですが、Qiitaの記事作成が今日で三日連続です(笑)
今日は仕事終わりなので、サクッと記事を書いて終わりたい…(笑))
##コーディング
とりあえず、サンプルのコードを下記に貼り付けます。
Form1.cs と JavaScriptを仕込んだ sample.htmlだけなので、察しの良い人ならサンプルのコードを見ただけで十分かもしれません。
（.htmlに関しては適当に書いたので、あまり深く突っ込まないでください(笑))

```C#:Form1.cs
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SampleWebView2Form
{
    public partial class Form1 : Form
    {
        /// <summary>webviewのコントロール（わかりやすい様に、デザイナーを使わずにコード側で実装します。）</summary>
        private WebView2 WebView = new WebView2
        {
            //個人の環境に合わせて下さい
            Source = new Uri("file:///C:/Users/name/Desktop/sample.html"),
        };

        /// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary>
        private JsToCs CsClass = new JsToCs();

        public Form1()
        {
            this.Controls.Add(WebView);
            InitializeComponent();

            //WebView2のサイズをフォームのサイズに合わせる
            WebView.Size = this.Size;
            this.SizeChanged += Form1_SizeChanged;

            //WebView2のロード完了時のイベント
            WebView.NavigationCompleted += WebView_NavigationCompleted;
        }

        /// <summary>WebView2のロード完了時</summary>
        private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            try
            {
                if (WebView.CoreWebView2 != null)
                {
                    //JavaScriptからC#のメソッドが実行できる様に仕込む
                    WebView.CoreWebView2.AddHostObjectToScript("class", CsClass);

                    //JavaScriptの関数を実行
                    CsToJs();
                }
                else MessageBox.Show("CoreWebView2==null");
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        /// <summary>Jsのメソッドを実行</summary>
        private async void CsToJs()
        {
            //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする
            string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")");
            MessageBox.Show("Jsからの戻り値＞" + str1);
        }

        /// <summary>サイズ変更時のイベントでWebView2のサイズをフォームに合わせる</summary>
        private void Form1_SizeChanged(object sender, EventArgs e)
        {
            WebView.Size = this.Size;
        }
    }

    //↓属性設定が無いとエラーになります
    /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary>
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class JsToCs
    {
        public void MessageShow(string strText)
        {
            MessageBox.Show("Jsからの呼び出し>" + strText);
        }
    }
}
```

```html:sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
    <script language="javascript" type="text/javascript">
        //C#から呼び出すための関数
        function func1(str1) {
            alert("C# called>" + str1);
            return "success"
        }
        function ButtonClick() {
            //C#の関数の実行
            chrome.webview.hostObjects.class.MessageShow("Js send text");
        }
    </script>
</head>
<body>
    <h1>sample</h1>
    <input type="button" value='send Message' onclick="ButtonClick();"/>
    <script></script>
</body>
</html>
```

##解説
**C#->JavaScript**
C#からJavaScript内の関数を呼び出すには、`ExecuteScriptAsync()`を使います。この関数は、先日書いた記事で`ExecuteScriptAsync("alert(\"message\")");`的な使い方をしたのですが、引数の中身がJavaScriptとして処理できるのであれば、JavaScriptのコード内の独自で作成した関数でも実行できます。
注意点として、`ExecuteScriptAsync()`は Task なので、戻り値を取得する場合は`.Result`か`await`を使用することになるのですが、`.Result`を使用するとフリーズして処理が進まなくなるため、戻り値を取得するなら`await`を使用する必要があります。
サンプルコードでは、下記の部分が該当の箇所です。

```c#:Form1.cs
        /// <summary>Jsのメソッドを実行</summary>
        private async void CsToJs()
        {
            //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする
            string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")");
            MessageBox.Show("Jsからの戻り値＞" + str1);
        }
```
```html:sample.html
    <script language="javascript" type="text/javascript">
        //C#から呼び出すための関数
        function func1(str1) {
            alert("C# called>" + str1);
            return "success"
        }
    </script>
```
挙動としては下記画像の様になります。
・ロード直後にJavaScriptのアラート出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762492/59ee0438-f819-3f69-35f0-d15a7e4295d9.png)
・アラートを閉じると、メッセージボックス出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762492/d9d23bf0-f01d-b4dc-3bdb-27e4dfd253b9.png)
**JavaScript->C#**
JavaScriptからC#の呼び出しには、`AddHostObjectToScript()`を使ってC#内で作成した関数をJavaScriptに読み込ませます。
（`WebMessageReceived`を使う方法もあるらしいので、気になる方は調べてみてください）
サンプルコードでは、下記の部分が該当の箇所です。

```c#:Form1.cs
        /// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary>
        JsToCs CsClass = new JsToCs();
        //JavaScriptからC#のメソッドが実行できる様に仕込む
        WebView.CoreWebView2.AddHostObjectToScript("class", CsClass);

//～～～一部省略～～～
    /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary>
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class JsToCs
    {
        public void MessageShow(string strText)
        {
            MessageBox.Show("Jsからの呼び出し>" + strText);
        }
    }
```
```html:sample.html
    <script language="javascript" type="text/javascript">
        function ButtonClick() {
            //C#の関数の実行
            chrome.webview.hostObjects.class.MessageShow("Js send text");
        }
    </script>

<body>
    <input type="button" value='send Message' onclick="ButtonClick();"/>
</body>
```
挙動としては、ブラウザー内の「send Message」を押下してもらうと、C#のメッセージが出力される様になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762492/b35e53bd-3dd4-1e8b-0e78-864437f20fbb.png)
##まとめ
今回はこんなところです。
WebView2に関しての記事は昨日書いたのですが、JavaScriptからC#を実行する処理に関しては直近一週間くらい、ずっと放置だったので、その問題が解消して良かったです。

今日は特に書くことないですね（別に無くても良いんですが(笑)）
昨日書いた記事のリンクを一応貼り付けときます。
https://qiita.com/NagaJun/items/4925a63ce7b93b80639e
最後まで読んで頂き、ありがとうございました。