[win8] MetroアプリからDesktopアプリへWCFで接続する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3387
[win8] metro アプリケーションからデスクトップアプリにプロセス間通信する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3379
# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。
なところで、WCF を使ってプロセス間通信できることは確認できたのですが、WCF だと metro アプリのほうで web の参照設定をしないといけません。まぁ、製品的にサーバーが先に固定化されている場合はいいのですが、流動的に作っている場合は先にインターフェースを決めないといけないというのはちょっと酷です。
な訳で、Web API 風に GET コマンドのアドレスを使って metro アプリ(クライアント)から desktop アプリ(サーバー)へ送信できるようにします。
■クライアントの metro アプリ側
1 2 3 4 5 6 7 8 9 10 | private async void Button_Click_1( object sender, RoutedEventArgs e) { string text = textBox1.Text; string url = "http://localhost:8083/metro/method" ; HttpClient client = new HttpClient(); HttpResponseMessage res = await client.GetAsync(url + "/" + textBox1.Text); string response = await res.Content.ReadAsStringAsync(); textBox2.Text = response; } |
metro アプリのほうでは、アドレスを指定してサーバーに接続します。Web API の REST のように「http://localhost:8083/metro/method/masuda/1000」とか「http://localhost:8083/metro/method?name=masuda&num=1000」のように呼び出すことを想定します。ここのサンプルでは適当に呼出URLを作っているだけなので、URLを作るための適当なラッパを作ると良いでしょう。
応答は、XML 形式でもよいのですが、自由に。
■Web APIを提供する desktop アプリ
HttpListener.BeginGetContext メソッド (System.Net)
http://msdn.microsoft.com/ja-jp/library/system.net.httplistener.begingetcontext(v=vs.110).aspx
を参考にして、HttpListener クラスでサーバーを作ります。前回は、同期メソッドを使ったために backgroundWorker コンポーネントでスレッドを使いましたが、今回は非同期メソッドの BeginGetContext、EndGetContext メソッドを使います。上記のサンプルコードではコールバック関数が static になっていますが、下記のように普通の内部メソッドを使うことができます。つーか、内部メソッドを使ったほうが、ListBox などの GUI にアクセスできるので便利かと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | using System.Net; using System.IO; namespace SampleLocalWebApiServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); } HttpListener listener; private void button1_Click( object sender, EventArgs e) { string url = "http://*:8083/metro/" ; // 開始 listener = new HttpListener(); listener.Prefixes.Add(url); listener.Start(); listBox1.Items.Add( "サーバー開始" ); listener.BeginGetContext(ListenerCallback, listener); } public void ListenerCallback(IAsyncResult result) { // HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context; try { context = listener.EndGetContext(result); } catch { // stop メソッドで例外が発生するので、対処 return ; } // var content = listener.GetContext(); var req = context.Request; var url = req.RawUrl; var res = context.Response; listBox1.Items.Add( "受信" ); var output = new StreamWriter( res.OutputStream ) ; output.WriteLine( string .Format( "called {0}" , url)); output.Close(); // 次の受信の準備 listener.BeginGetContext(ListenerCallback, listener); } private void button2_Click( object sender, EventArgs e) { // 終了 listener.Stop(); listBox1.Items.Add( "サーバー終了" ); } } } |
サーバーの停止なのですが、Stop か、Abort を呼び出します。が、ちょっと面倒なのは、Stop メソッドを呼び出した途端にコールバックが呼び出されるんですよね…で、EndGetContext メソッドの呼び出し時に例外が発生してしまうので、コードでは try-catch で、回避しています。異常終了の場合と区別がつかないので、もうちょっとなんとかしたいところですね。
呼び出した URL は、RawUrl プロパティで取得できます。URL プロパティでもいいのですが、RawURL のほうが、サーバー名とポート名を削ってくれるので、処理が楽なのです(名前が逆っぽいのは、見ないことにしよう)。
■実行してみる
サーバーを管理者権限で動かして、metro app からつなげてみます。
無事接続できていますね。サーバー側で適当に振り分けてやれば、Web API として動かすことができます。URL Encode/Decodeすれば文字列は簡単です。複雑な場合は POST で送って、適当なクラスでラップすればよいですかね。というか、シリアライズ機能を使えば ok かも。