気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

前回のTipsでSilverlightを「Google App Engine」にホストする
方法を紹介しました。

今回は、ホストされたSilverlightアプリと「Google App Engine」上のPythonモジュール間で
Rest通信を行う方法を紹介します。

アプリの内容はサーバ側で出力した、氏名を管理するためのXMLの内容をSilverlightの
TextBoxに表示するというシンプルなものです。

サーバ側のPythonモジュールはXMLを出力するだけのシンプルなものです。

(本格的な「Google App Engine」を使用したアプリを開発する場合は、Python側で様々なデータを簡単に
XML形式で出力する方法が課題になってきそうです。それはまた別途記事を書いてみたいと思います。)

  #!-*- coding:utf-8 -*-
  from google.appengine.ext import webapp
  from google.appengine.ext.webapp.util import run_wsgi_app

  class MainPage(webapp.RequestHandler):
    def get(self):
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write('<?xml version="1.0" encoding="UTF-8" ?>')
      self.response.out.write('<Person>')
      self.response.out.write('<LastName>田中</LastName>')
      self.response.out.write('<FirstName>一郎</FirstName>')
      self.response.out.write('</Person>')

  application = webapp.WSGIApplication(
                                     [('/', MainPage)],
                                     debug=True)

  def main():
    run_wsgi_app(application)

  if __name__ == "__main__":
    main()

出力されるXMLはこんな感じになります。

  <?xml version="1.0" encoding="UTF-8" ?> 
  <Person>
  <LastName>田中</LastName> 
  <FirstName>一郎</FirstName> 
  </Person>

次はSilverlight側です。C#は元々、XMLを扱うための強力なAPIを備えています。他の言語でも
備えているようなDomなどの他、XMLとC#のクラスを相互変換(シリアライズ/デシリアライズ)
する仕組みを持っています。

今回のサンプルではそのデシリアライズの仕組みを使ってXMLから値を取得するようにしました。

まずは、XMLの内容を管理するための「Person」クラスを定義します。後ほど、サーバから取得したXMLの情報から
このクラスのインスタンスを作成する処理を行います。

[Person.cs]
  public class Person
  {
     public String LastName { get; set; }
     public String FirstName { get; set; }
  }

クラス名がXMLのルート要素に、プロパティ名がXMLの子要素にそれぞれ対応しています。
クラス名や、プロパティ名は原則、XMLの要素名と同じにしておく必要があるのですが、
[System.Xml.Serialization.XmlElement()]属性などを指定することによって、異なる
プロパティ名をつけることも可能です。

  [System.Xml.Serialization.XmlElement("姓")]
  public String LastName { get; set; }

※上記コードはC# 3.0から導入された「自動プロパティ」を使用しています。

次に、サーバからXMLをダウンロードする部分です。

Http通信には「WebRequest」クラスを使用しました。Getによる単純なデータ取得の場合、
「WebClient」クラスの方が簡単に扱えるのですが、本格的なアプリを開発する際は、
POSTする必要も出てくると思ったのでこちらにしました。

まずはリクエストを発行する部分。

[MainPage.xaml.cs]
  private String _svrUrl = "http://localhost:8080/";

  //Loadedイベントのハンドラ
  private void OnLoaded(Object sender, RoutedEventArgs e)
  {
      try
      {
          //リクエストを生成する
          WebRequest req = WebRequest.Create(new Uri(_svrUrl));
          req.Method = "GET";

          //リクエストを発行する
          req.BeginGetResponse(new AsyncCallback(GetResponseCallback), req);
      }
      catch { }
  }

コンストラクタか、Loadedイベントのハンドラでやればよいか分からなかったので両方でやってみましたが
今回のサンプルでは結果は同じでした。

.Net Frameworkの「WebRequest」には同期のメソッドであるGetResponse()があるのですが、
Silverlightでは非同期のBeginGetResponse()しかないので、こちらを使っています。

非同期処理は、スレッドを使ってまとめてやりたいので、個人的には、
同期メソッドも提供してほしいです。

次がレスポンスが帰った際のコールバックメソッド。

[MainPage.xaml.cs]
  //サーバからレスポンスが帰るとコールされるコールバックメソッド
  private void GetResponseCallback(IAsyncResult ar)
  {
      try
      {
          //レスポンスオブジェクトを取得する
          WebRequest req = ar.AsyncState as WebRequest;
          WebResponse res = req.EndGetResponse(ar);

          //コンテンツをダウンロードするためのストリーム
          Stream stream = res.GetResponseStream();

          //別スレッドを起動して、ダウンロードを開始する
          ThreadPool.QueueUserWorkItem(new WaitCallback(ReadContents), stream);
      }
      catch { }
  }

コンテンツのダウンロードも非同期でやりたいので、ここでThreadPoolを使っています。

[MainPage.xaml.cs]
  //Streamからコンテンツを読み込む
  private void ReadContents(Object stream)
  {
      Person person = null;

      using (StreamReader reader = new StreamReader(stream as Stream))
      {
          //※「System.Xml.Serialization.dll」を参照設定する必要がある
          XmlSerializer serializer = new XmlSerializer(typeof(Person));

          //XMLからPersonクラスのインスタンスを生成する
          person = serializer.Deserialize(reader) as Person;
      }

      //コントロールの生成スレッドを呼び出す
      Dispatcher.BeginInvoke(
          () => {
              //画面に取得した値を設定する
              _lastName.Text = person.LastName;
              _firstName.Text = person.FirstName;
          }
      );
  }

ダウンロードしたXMLの内容から、先ほど定義したPersonクラスのインスタンスを生成しています。
そして、別スレッドからコントロールにアクセスするとエラーが発生するので、コントロールの生成スレッドを
呼び出して、TextBoxに値を設定しています。WindowsフォームでいうInvoke()です。
(匿名メソッドはラムダ形式で記述しています。)

このアプリはExpressの自動生成ページでデバックするとセキュリティ違反になるので
「Google App Engine」の開発用サーバ以下に配置して実行する必要があります。
デバックが少々不便でした。何か良い方法があるといいのですが。

ソース全体




コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://csfun.blog49.fc2.com/tb.php/36-efd07365