WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その４

# はじめに
久しぶりにネタが出来たので書いてみました。

* [WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる](https://qiita.com/yaju/items/97b61d0d8bc3f9ed044d)
* [WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その２](https://qiita.com/yaju/items/6fc1b72e60dbcc031669)
* [WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その３](https://qiita.com/yaju/items/b2c64f364efd3e347359)

# 問題点
お仕事では入力項目に入ったバーコードリーダーの値をEnterキーイベント時に取得するようにしていました。「[その３のキー押下の処理](https://qiita.com/yaju/items/b2c64f364efd3e347359#%E3%82%AD%E3%83%BC%E6%8A%BC%E4%B8%8B%E3%81%AE%E5%87%A6%E7%90%86)」を利用しています。

ところが50回に数回くらいサーバーに送られた値と違う(桁数が足りない)現象が見受けられました。
※RS-232Cの入力データをキー入力に変換するソフトを使用していたり、処理能力が低いPCなどで発生しているので一般的なPCでは発生することはないと思われます。

# 原因
CefSharpのWebコンポーネント側での入力途中のタイミングで、先にEnterキーイベントで入力値を取得してしまうことが原因と思われます。

IEのWebブラウザコンポーネントでは`previewkeydown`イベントが専用にあったため問題にならなったのかな、やっていることは一緒なはずなんだが・・・

# 対応
サーバーに送られた値は正しいのでリクエストの値を取得できればいいと考えました。
調べるとCefSharpのWebコンポーネントには、`RequestHandler`があることが分かりました。

* [WPFでCefSharp（Chromiumの.NET向け実装）を使う - 3](http://ghken.hatenablog.com/entry/2017/05/02/001717)
* [CefSharp の IRequestHandler は最低限どう実装すればよいか](https://peta.okechan.net/blog/archives/4672)
* [CefSharpでリダイレクトURLを取得する方法](https://kagasu.hatenablog.com/entry/2018/02/11/021040)
* [ClientHandler::OnBeforeBrowse - no POST data?](https://magpcss.org/ceforum/viewtopic.php?f=6&t=15750)
* [C# (CSharp) IRequestCallback Examples](https://csharp.hotexamples.com/examples/-/IRequestCallback/-/php-irequestcallback-class-examples.html)

サンプルは、「[WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その２](https://qiita.com/yaju/items/6fc1b72e60dbcc031669)」で使用した「[Keisan 生活や実務に役立つ計算サイト - 体脂肪率](https://keisan.casio.jp/exec/system/1161228728)」で、サイト読み込み完了直後に体脂肪率なら身長と体重を自動入力し、リクエストされた身長と体重と体脂肪率の結果を表示します。

```vb:VB版
' リクエストデータ格納用
Private _postData As Hashtable = New Hashtable()

' リクエストイベント処理登録
_webBrowser.RequestHandler = New RequestHandler(Me)

Private Sub OnLoadingStateChanged(sender As Object, e As LoadingStateChangedEventArgs)
    If Not e.IsLoading Then
        Invoke(New MethodInvoker(
                Sub()
                    '_webBrowser.Focus()
                    SetFocus(_webBrowser.Handle)
                End Sub))

        ' 読込完了処理を記述
        Dim jsScript As String
        jsScript = String.Format("document.getElementById('var_身長').focus();")
        _webBrowser.ExecuteScriptAsync(jsScript)

        Dim anser As String = ""
        If _currentAddress.IndexOf("keisan") >= 0 Then
            jsScript = "document.getElementById('ans1').innerText;"
            _webBrowser.EvaluateScriptAsync(jsScript).ContinueWith(
                Sub(x)
                    Dim response = x.Result

                    If response.Success AndAlso response.Result IsNot Nothing Then
                        anser = response.Result.ToString()
                        If anser.Trim = "" Then
                            jsScript = String.Format("document.getElementById('var_身長').value = '{0}';", 180)
                            jsScript &= String.Format("document.getElementById('var_体重').value = '{0}';", 54)
                            jsScript &= "document.getElementById('executebtn').click();"
                            _webBrowser.ExecuteScriptAsync(jsScript)
                        Else
                            MessageBox.Show(String.Format("身長={0} 体重={1} BMI={2}", _postData("var_身長"), _postData("var_体重"), anser))
                        End If
                    End If
                End Sub)
        End If
    End If
End Sub

' リクエストのデータセット処理
Public Function SetRequestData(url As String, requestBody As String) As Boolean
    Dim aaa As String

    If url.IndexOf("/exec/system/1161228728") >= 0 Then
        If requestBody.IndexOf("=") <> -1 Then
            Dim ary As String() = requestBody.Split("&")
            For i As Integer = 0 To ary.Length - 1
                Dim param As String() = ary(i).Split("=")
                _postData.Add(Web.HttpUtility.UrlDecode(param(0)), Web.HttpUtility.UrlDecode(param(1)))
            Next

            Return True
        End If
    End If

    Return False
End Function
```

```vb:VB版
Public Class RequestHandler
    Implements IRequestHandler
    Private _frm As frmMain

    ' コンストラクタ
    Public Sub New(frm As frmMain)
        _frm = frm
    End Sub

    ' リクエストが投げられる前に呼ばれる処理
    Private Function OnBeforeResourceLoad(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, callback As IRequestCallback) As CefReturnValue Implements IRequestHandler.OnBeforeResourceLoad
        If request.Method = "POST" Then
            Using postData = request.PostData
                Dim elements = postData.Elements
                Dim charSet = request.GetCharSet()

                For Each element In elements
                    If element.Type = PostDataElementType.Bytes Then
                        If _frm.SetRequestData(browserControl.Address, element.GetBody(charSet)) Then
                            Exit For
                        End If
                    End If
                Next
            End Using
        End If

        Return CefReturnValue.[Continue]
    End Function

    Private Function OnOpenUrlFromTab(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean) As Boolean Implements IRequestHandler.OnOpenUrlFromTab
        Return False
    End Function

    Private Function OnCertificateError(browserControl As IWebBrowser, browser As IBrowser, errorCode As CefErrorCode, requestUrl As String, sslInfo As ISslInfo, callback As IRequestCallback) As Boolean Implements IRequestHandler.OnCertificateError
        Return False
    End Function

    Private Sub OnPluginCrashed(browserControl As IWebBrowser, browser As IBrowser, pluginPath As String) Implements IRequestHandler.OnPluginCrashed
    End Sub

    Private Function GetAuthCredentials(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, isProxy As Boolean, host As String, port As Integer,
        realm As String, scheme As String, callback As IAuthCallback) As Boolean Implements IRequestHandler.GetAuthCredentials
        callback.Dispose()
        Return False
    End Function
    Private Sub OnRenderProcessTerminated(browserControl As IWebBrowser, browser As IBrowser, status As CefTerminationStatus) Implements IRequestHandler.OnRenderProcessTerminated
    End Sub
    Private Sub OnRenderViewReady(browserControl As IWebBrowser, browser As IBrowser) Implements IRequestHandler.OnRenderViewReady
    End Sub

    Private Function OnQuotaRequest(browserControl As IWebBrowser, browser As IBrowser, originUrl As String, newSize As Long, callback As IRequestCallback) As Boolean Implements IRequestHandler.OnQuotaRequest
        Return False
    End Function

    Private Function OnProtocolExecution(browserControl As IWebBrowser, browser As IBrowser, url As String) As Boolean Implements IRequestHandler.OnProtocolExecution
        Return False
    End Function

    Private Function OnResourceResponse(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse) As Boolean Implements IRequestHandler.OnResourceResponse
        Return False
    End Function

    Private Sub OnResourceLoadComplete(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse, status As UrlRequestStatus,
        receivedContentLength As Long) Implements IRequestHandler.OnResourceLoadComplete
    End Sub

    Private Function GetResourceResponseFilter(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse) As IResponseFilter Implements IRequestHandler.GetResourceResponseFilter
        Return Nothing
    End Function

    Private Sub OnResourceRedirect(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse, ByRef newUrl As String) Implements IRequestHandler.OnResourceRedirect
    End Sub

    Private Function OnBeforeBrowse(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, userGesture As Boolean, isRedirect As Boolean) As Boolean Implements IRequestHandler.OnBeforeBrowse
        Return False
    End Function

    Private Function OnSelectClientCertificate(chromiumWebBrowser As IWebBrowser, browser As IBrowser, isProxy As Boolean, host As String, port As Integer, certificates As X509Certificate2Collection, callback As ISelectClientCertificateCallback) As Boolean Implements IRequestHandler.OnSelectClientCertificate
        Return True
    End Function

    Private Function CanGetCookies(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest) As Boolean Implements IRequestHandler.CanGetCookies
        Return True
    End Function

    Private Function CanSetCookie(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, cookie As Cookie) As Boolean Implements IRequestHandler.CanSetCookie
        Return True
    End Function

End Class
```

C#版はSharpDevelopでVBから変換したものです。

```csharp:C#版
// リクエストデータ格納用
private Hashtable _postData = new Hashtable();

// リクエストイベント処理登録
_webBrowser.RequestHandler = new RequestHandler(this);

private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
	if (!e.IsLoading) {
		//_webBrowser.Focus()
		Invoke(new MethodInvoker(() => { SetFocus(_webBrowser.Handle); }));

		// 読込完了処理を記述
		string jsScript = null;
		jsScript = string.Format("document.getElementById('var_身長').focus();");
		_webBrowser.ExecuteScriptAsync(jsScript);

		string anser = "";
		if (_currentAddress.IndexOf("keisan") >= 0) {
			jsScript = "document.getElementById('ans1').innerText;";
			_webBrowser.EvaluateScriptAsync(jsScript).ContinueWith(x =>
			{
				dynamic response = x.Result;

				if (response.Success && response.Result != null) {
					anser = response.Result.ToString();
					if (string.IsNullOrEmpty(anser.Trim())) {
						jsScript = string.Format("document.getElementById('var_身長').value = '{0}';", 180);
						jsScript += string.Format("document.getElementById('var_体重').value = '{0}';", 54);
						jsScript += "document.getElementById('executebtn').click();";
						_webBrowser.ExecuteScriptAsync(jsScript);
					} else {
						MessageBox.Show(string.Format("身長={0} 体重={1} BMI={2}", _postData("var_身長"), _postData("var_体重"), anser));
					}
				}
			});
		}
	}
}

// リクエストのデータセット処理
public bool SetRequestData(string url, string requestBody)
{
	string aaa = null;

	if (url.IndexOf("/exec/system/1161228728") >= 0) {
		if (requestBody.IndexOf("=") != -1) {
			string[] ary = requestBody.Split("&");
			for (int i = 0; i <= ary.Length - 1; i++) {
				string[] param = ary[i].Split("=");
				_postData.Add(Web.HttpUtility.UrlDecode(param[0]), Web.HttpUtility.UrlDecode(param[1]));
			}

			return true;
		}
	}

	return false;
}
```

```csharp:C#版
// リクエストハンドラークラス
public class RequestHandler : IRequestHandler
{
    private frmMain _frm;

    // コンストラクタ
    public RequestHandler(frmMain frm)
    {
        _frm = frm;
    }

    // リクエストが投げられる前に呼ばれる処理
    CefReturnValue IRequestHandler.OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
    {
        if (request.Method == "POST")
        {
            using (var postData = request.PostData)
            {
                var elements = postData.Elements;
                var charSet = request.GetCharSet();

                foreach (var element in elements)
                {
                    if (element.Type == PostDataElementType.Bytes)
                    {
                        if (_frm.SetRequestData(browserControl.Address, element.GetBody(charSet)))
                            break;
                    }
                }
            }
        }

        return CefReturnValue.Continue;
    }

    bool IRequestHandler.OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
    {
        return false;
    }

    bool IRequestHandler.OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
    {
        return false;
    }

    void IRequestHandler.OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath)
    {
    }

    bool IRequestHandler.GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
    {
        callback.Dispose();
        return false;
    }

    void IRequestHandler.OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status)
    {
    }

    void IRequestHandler.OnRenderViewReady(IWebBrowser browserControl, IBrowser browser)
    {
    }

    bool IRequestHandler.OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback)
    {
        return false;
    }

    bool IRequestHandler.OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url)
    {
        return false;
    }

    bool IRequestHandler.OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
    {
        return false;
    }

    void IRequestHandler.OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength)
    {
    }

    public void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
    {
    }

    public IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
    {
        return null;
    }

    public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
    {
        return false;
    }

    public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
    {
        return true;
    }

    public bool CanGetCookies(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request)
    {
        return true;
    }

    public bool CanSetCookie(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, CefSharp.Cookie cookie)
    {
        return true;
    }
}
```

# 結果
リクエストした身長と体重を取得することが出来ています。

![CefSharp4結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/f7026160-84c4-5f1e-c076-309095898afb.png)

# 最後に
リクエストした値をクライアント側で取得することはあまりない用途だと思います。この値をシリアルポートに送信して機器を動かすということをしています。

一般的に`OnBeforeResourceLoad`の用途はリクエストヘッダーの値を変更するとかくらいでしょうかね。