Webブラウザとクライアントアプリの同期方法

まあこの業界にいたら日常的に体験することであるが、企業向けのソフトを作っていると、時々妙な要望を受けることがある。もうちょっと普通な実装をさせてくれよと思うのだが、なぜこのような要求がまかり通るのか理解しかねる。

今回のタイトルの件もそういう感じで、客先からの要求は「Windowsのクライアントアプリと開いてるブラウザ上のページの情報を常に同期しておきたい。つまり項目Aを削除したらブラウザ上からも即座に項目Aを削除させたい。みたいな。IEのバージョンは9で頼むよ」というものだった。「無理だったら全部クライアントで実装してもいいよ」とも言っていた。

私は、なぜクライアントとWebアプリで同じような情報を二つ表示させ、さらにどちらの情報も整合性を取るなどという面倒くさいことをしなければならないのか、皆目見当がつかない。

同じような情報を二か所で表示させるというのはユーザー側から見てもわかりにくいだろう。同じような情報は統合してどちらか一方で表示させるべきだと思う。

こういう意味不明な要求仕様が出てくる背景には以下のようなプロセスが絡んでいるのではないかと私は思っている。

まず、解決しなければならない問題Aがあり、Aを解決するための解決策Bが必要である。しかし、手段Bを実現するのはなかなか難しい。しかし、Cという方法とDという方法を組み合わせれば何とか解決策Bを実現できる。もしCとDがダメであればFという方法でもなんとかなるので、まあダメならそっちのオプションを採用させよう。みたいな流れで決まっているのだろう。

しかし、たいてい、システムとしてあるべき姿なのは問題Aを解決するためには解決策Bよりも優れた解決策Zが存在する。ではなぜ解決策Zを採用しないのかというと、手段が目的化しているためである。つまり、解決策Bを実現することが目的になってしまっていて、解決策Bが本当にいいのかどうかという議論がなされていないのだ。

私の経験上、上記のような場面は数多あった。新入社員のころは「それっておかしくねえか」とバカ正直に指摘していたのだけど、最近はもう無視している。指摘したらしたで、特に年食ったおっさんは「俺のやり方が最善」と思っているので、いろいろ細かい難癖をつけてきて持論を曲げようとしないし、それならば適当にハイハイ言って遠回りな方法を選択して、「これは実装がむずかしいっすねー」なんか言って追加費用をせしめればいいだけなのである。

こうしてやる気のある新入社員はやる気をなくし、日本のメーカーはどんどんダメになる。困ったもんです。こまったもんだが、どうしようもないので諦めてる。ソフト屋のいいところは、その気になれば一人でも新しいサービスを立ち上げて金を稼げるという点であって、仕事がつまらなければ自分で面白いことを勝手気ままにできてしまうというところだと思っている。

なんか話が大幅にずれましたが、「Webブラウザとクライアントアプリの同期方法」を考えましょう。

案1:socket.ioでサーバを介して同期

socket.ioならばWebSocketに対応していない古いIEでも、FlashSocketやロングポーリングなどを使ってうまいことWebSocketっぽいことをやってくれます。

また、Socket.ioは.NET向けのクライアントSocketIO4Net.Clientがあるので、これを使ってクライアントとも同期してくれます。

ブラウザのセッションと特定のクライアントをどのように紐づけるかはシステムの要件によってそれぞれだと思うのでここでは詳しく述べませんが、たとえばランダムに生成したトークンをつかったり、ユーザー名で判別したり、ということが考えられます。

サーバ側の実装は結構面倒だと思います。苦労する割に実りがない作業なので私は実装したくありません。

案2:WebBrowserコントロールをクライアントアプリに埋め込む

ブラウザじゃなくてアプリ上に埋め込んだWebBrowserコントロールでも良いならば、もうちょっと簡単です。WebBrowserコントロールにはInvokeScriptというメソッドが公開されていて、これでページ中の任意のJavaScript関数を叩くことができます。結果も受け取れます。

あとはjQueryなり何なりを応用して、ロジックをWeb側で実装して簡単なインタフェースをクライアントアプリ側に公開すればよいでしょう。

サンプルコードは以下です。WPFです。やる気がないのでめっちゃ適当。

サーバー側:
[html]
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script src="javascripts/jquery-1.11.1.js"></script>
<script type="text/javascript">
function action1(){
$("#result").append("action1 executed");
}
function action2(){
return true;
}
</script>
</head>
<body>
<textarea id="result">results:</textarea>
</body>
</html>
[/html]

クライアント側:
[csharp]
private void go_Click(object sender, RoutedEventArgs e)
{
Browser.Navigate(new Uri(url.Text));
}

private void action1_Click(object sender, RoutedEventArgs e)
{
Browser.InvokeScript("action1");
}

private void action2_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Browser.InvokeScript("action2").ToString());
}
[/csharp]

クライアント側(view):
[xml]
<StackPanel>
<TextBox x:Name="url" />
<Button x:Name="go" Click="go_Click">GO</Button>
<Button x:Name="action1" Click="action1_Click">Action1</Button>
<Button x:Name="action2" Click="action2_Click">Action2</Button>
<WebBrowser x:Name="Browser"
Width="600"
Height="400" />
</StackPanel>
[/xml]

実行結果:
wbc

action1を押すと「action1 executed」とページ中に表示されます。action2と押すと戻り値(true)をMessageBoxで表示させます。

以上。眠い。