mikeo_410


SilverlightとNuSoap


  お菓子検索に使った、Web API の代理を行う Webサービスのことを書きます。
  お菓子検索のサンプルプログラムは、左図をクリックするとポップアップウインドウを開いて実行します。
  お菓子の虜 Web API の検索機能を使っています。

  お菓子検索だけでなく、多くの Web API が Silverlight から接続できません。これは、サーバーが拒否しているわけではなく、こちらが(Silverlightが)勝手に接続をしないだけなので、Silverlight アプリケーションを置くサーバに中継をしてもらおうと言うものです。
  ブラウザを実行しているパソコンから直接接続できない、ドメイン間ポリシーの件は、「Silverlightの入出力」を参照ください。

1.NuSoap

  NuSoap は、PHPでWebサービスが作れるライブラリです。PHPの使える環境に展開すれば、インストールなしに使えます。
  これを使う理由は、お菓子検索のサンプルが置かれているのが、Linux、Apachのレンタルサーバだからです。

  1. UTF-8 の設定をすること
    ISO-8859-1がデフォルトで、UTF-8で使うためには、NuSoap.php の2か所を書き換える必要があります。
    $soap_defencoding で設定するようになっており、UTF-8がコメントになっています。
    もう一か所は、function serialize の、XMLテキスト出力するときの開始行の文字列です。
  2. 無料のレンタルサーバは、表示に広告を入れます。サイトによっては、WSDL記述にも広告が入り正常に行かないことがあります。

2.Webサービス

  2つの関数があります。テキストを受信するためのものとバイナリを受信するためのものです。

  以下は、テキストを受信するもので、URL で指定したページが文字列として取得されます。
  今回使用した Web API では、検索条件を URL の後半に付加して送ります。
  検索結果は、XMLテキストとして受信されます。イメージは、そのファイルの URL がXMLテキストの中に書かれています。
  検索結果の中にはイメージ自体は含まれていません。

  1. function ReadPage($url){
  2.     $s=file_get_contents($url);
  3.     return $s;
  4. }

   以下は、バイナリを受信するためのもので、イメージファイルの受信に使います。

  1. function ReadImage($url) {
  2.     $s=file_get_contents($url);
  3.     return base64_encode($s);
  4. }

  全体は以下の通りです。

  1. <?php
  2.     require_once('lib/nusoap.php');
  3.     $urn = 'http://mikeo410.wsp1.net/search_sweets/getfile';
  4.     $server = new soap_server();
  5.     $server->debug_flag=false;
  6.     $server->configureWSDL('getfile',$urn);
  7.     $server->wsdl->schemaTargetNamespace=$urn;
  8.     $server->soap_defencoding ='utf-8';
  9.     
  10.     $server->register('ReadPage',
  11.         array('name' => 'xsd:string'),        // input parameter
  12.         array('return' => 'xsd:string'),      // output parameter
  13.         $urn,
  14.         false,
  15.         'rpc','literal','description1');
  16.     $server->register('ReadImage',
  17.         array('url' => 'xsd:string'),         // input parameter
  18.         array('return' => 'xsd:base64Binary'),// output parameter
  19.         $urn,
  20.         false,
  21.         'rpc','literal','description1');
  22.     
  23.     function ReadPage($url){
  24.         $s=file_get_contents($url);
  25.         return $s;
  26.     }
  27.     function ReadImage($url) {
  28.         $s=file_get_contents($url);
  29.         return base64_encode($s);
  30.     }
  31.     $HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
  32.     $server->service($HTTP_RAW_POST_DATA);
  33. ?>

3.サービス参照の追加

  Silverlight アプリケーションに「サービス参照」を追加して、この機能を使います。

3.1.サービスの確認

  前述の Web サービスを、getfile.php と名前を付けて、サーバーに置きました。同じ場所に、NuSoap の libディレクトリ以下もコピーします。

  ブラウザで、このgetfile.php を開くと、左図の表示になります。

  WSDL の部分をクリックすると、WSDLのXAMLテキストが表示されます。
  このときのアドレスバーの URL をコピーして置きます。

注意:

  1. ブラウザで開いたとき、上の図と異なり、phpのソースコードが表示されるかもしれません。
    それは、httpサーバーが、phpファイルを実行しないような設定になっているためです。
  2. NuSoapは、ISO-8859-1がデフォルトで、UTF-8で使うためには、NuSoap.php の2か所を書き換える必要があります。
  3. 無料のレンタルサーバに置いているのですが、WSDL にも広告が挿入されて上手くいかないサイトもあります。

3.2.Visual Stduio の操作

  ソリューションエクスプローラでプロジェクトを選んで、右ボタンメニューから「サービス参照の追加」を選びます。
  ブラウザで WSDL を開いたときにコピーして置いた URL をアドレスに貼りつけます。
  「移動」ボタンを押します。

  左図のように、Web サービスの内容が表示されます。

  「OK」ボタンを押します。

  ソリューションエクスプローラには、「Service References」と、ServiceReferences.ClientConfig ファイルが追加されます。

  ソリューションエクスプローラには表示されませんが、プロジェクトのディレクトリには、、Service References/ServiceReference1 ディレクトリが作られ、Reference.csを含むいくつかのファイルが作られます。
  この、Reference.cs が、サービスとの通信に使われるクラスで、引数や戻り値のマーシャリングを行ってくれます。

  このように、自動的に必要なものが作られるのですが、一つ問題がありました。
  この問題は、この例が、SketchFlow プロジェクトで作られているためのもののです。おそらく単一のプロジェクトから成るソリューションなら問題ないのだと推測します。


 

   問題は、実際に Web API を呼び出すと、左図のようなメッセージが表示されて、検索できないことです。
  ServiceReferences.ClientConfig ファイルがXAPパッケージに含まれないことが原因です。
  XAPは単にフォルダをZIP圧縮したものだと言うので、解凍して加えて見ましたが、再圧縮したものはXAPとして機能しないようです。

  結論は、ルートのプロジェクトに、ServiceReferences.ClientConfig ファイルをコピーして置くと、XAPに入るようになると言うことでした。

  SketchFlow プロジェクトを作ると、ソリューション内に2つのプロジェクトができます。
  一つは、app.xaml を含むルートにあたるもので、他方は、MainPage.xamlを含んで「ページ」に相当します。それぞれ、アセンブリ(DLL) になります。Web API を使うのは後者なので、後者に「サービス参照の追加」を行い、ServiceReferences.ClientConfig ファイルも後者に加えられます。
  この、ServiceReferences.ClientConfig ファイルを、ソリューションエクスプローラ上で、前者のプロジェクトにコピーするとXAPパッケージに含まれるようになりました。
  サーバー上のPHPファイルと、2つのServiceReferences.ClientConfig ファイルの整合性を維持しないといけません。

4.Web API のアクセス

  「サービス参照の追加」で作られた、ServiceReference1.getfilePortTypeClient クラスのReadPageAsync()を使って要求を送ります。このメソッドの名前は、PHPのサービスのメソッドの名前から作られていて、PHPの ReadPage が呼び出されることが推測できます。
  受信の完了時の処理を行うハンドラを指定しますが、"+=" までキー入力してTABを2回押せば自動的にハンドラのひな型まで作ってくれます。

  1. string s = "http://www.sysbird.jp/toriko/api/?apikey=guest&keyword=" + string_part1.Text;
  2. try
  3. {
  4.     ServiceReference1.getfilePortTypeClient c = new SilverlightPrototype1Screens.ServiceReference1.getfilePortTypeClient();
  5.     c.ReadPageCompleted += new EventHandler<SilverlightPrototype1Screens.ServiceReference1.ReadPageCompletedEventArgs>(c_ReadPageCompleted);
  6.     Uri uri = new Uri(s);
  7.     c.ReadPageAsync(uri.AbsoluteUri);
  8. }
  9. catch (Exception ex)
  10. {
  11.     MessageBox.Show(ex.Message.ToString());
  12. }

  受信完了を処理するハンドラでは、引数 e から受信した文字列が得られます。
  e.Result が string 型で、Web API の応答のXMLテキストが入っています。
  下のハンドラのサンプルは、XMLをデコードして、ListBoxの行を示すデータに詰め替えているので冗長になっています。

  1. List<SweetsItem> list = new List<SweetsItem>();
  2. static Queue<SweetsItem> queue = new Queue<SweetsItem>();
  3. void c_ReadPageCompleted(object sender, SilverlightPrototype1Screens.ServiceReference1.ReadPageCompletedEventArgs e)
  4. {
  5.     if (e.Error == null)
  6.     {
  7.         if (e.Result.Length <= 0)
  8.         {
  9.             MessageBox.Show("データなし");
  10.             return;
  11.         }
  12.         XDocument doc = XDocument.Parse(e.Result);
  13.         list.Clear();
  14.         queue.Clear();
  15.         foreach (XElement xe in doc.Root.Elements("item"))
  16.         {
  17.             SweetsItem si = new SweetsItem();
  18.             si.maker = (string)xe.Element("maker");
  19.             si.name = (string)xe.Element("name");
  20.             si.kana = (string)xe.Element("kana");
  21.             si.price = (string)xe.Element("price");
  22.             si.type = (string)xe.Element("type");
  23.             si.tag = (string)xe.Element("tag");
  24.             si.image_uri = new Uri(((string)xe.Element("image")).Trim());
  25.             si.comment = (string)xe.Element("comment");
  26.             list.Add(si);
  27.             si.SetImage();
  28.         }
  29.     }
  30.     else
  31.     {
  32.         MessageBox.Show(e.Error.ToString());
  33.     }
  34. }

  受信したXMLには、イメージの参照URLが書かれているので、これを別途読み込みます。
  処理は、XMLテキストを受け取る処理と同じ手順です。違いは、受信完了のハンドラが受け取る e.Result が byte[]型だという点だけです。

  1. try
  2. {
  3.     Debug.WriteLine(image_uri.AbsoluteUri);
  4.     ServiceReference1.getfilePortTypeClient c = new SilverlightPrototype1Screens.ServiceReference1.getfilePortTypeClient();
  5.     c.ReadImageCompleted += new EventHandler<SilverlightPrototype1Screens.ServiceReference1.ReadImageCompletedEventArgs>(c_ReadImageCompleted);
  6.     c.ReadImageAsync(image_uri.AbsoluteUri);
  7. }
  8. catch (Exception ex)
  9. {
  10.     Debug.WriteLine(ex.Message.ToString());
  11. }

  下のハンドラのサンプルは、GIF を扱うために冗長になっています。

  1. void c_ReadImageCompleted(object sender, SilverlightPrototype1Screens.ServiceReference1.ReadImageCompletedEventArgs e)
  2. {
  3.     if ((e.Error == null) && (e.Result != null) && (e.Result.Length > 0) && (image_uri.AbsoluteUri.Length > 4))
  4.     {
  5.         MemoryStream stream = new MemoryStream(e.Result);
  6.         string ext = image_uri.AbsoluteUri.Substring(image_uri.AbsoluteUri.Length - 4, 4).ToLower();
  7.         BitmapSource bs = null; //ビットマップの出力先
  8.         if ((ext == ".jpg") || (ext == ".png"))
  9.         {
  10.             bs = new BitmapImage();
  11.             bs.SetSource(stream);
  12.         }
  13.         else if (ext == ".gif")
  14.         {
  15.             //DeGIFを準備
  16.             DeGIF.GifDecoder degif = new DeGIF.GifDecoder();
  17.             //デコード
  18.             DeGIF.Image image = new DeGIF.Image();
  19.             degif.Decode(image, stream);
  20.             //WriteableBitmap に変換
  21.             WriteableBitmap wb = DeGIF.ImageExtensions.ToBitmap(image);
  22.             bs = (BitmapSource)wb;
  23.         }
  24.         this.image = bs;
  25.         queue.Enqueue(this);
  26.     }
  27. }


mikeo_410@hotmail.com