Paradigm Shift Design

ISHITOYA Kentaro's blog.

JsonicとSilverlightの連係

ご無沙汰です。生きてます。
もう11月も終わり、クリスマスシーズン…
なんちゅー速さでしょうか。この一ヶ月は研究に没頭していました。
あまり記憶がござんせん。


さて、Silverlightに取り組み始めました。今日から。なのでそのメモ書き。
やりたいことは、サーバーにアクセスしてデータを取ってきてレンダリングする、という簡単なことです。


サーバーは、すでにJsonicを使って構築されているサービスがあるので、JSONでやりたいと思っています。
どうやらSilverlightはJSONに対応しているらしいので、何とかなりそうです。

環境設定

まずは、ソリューションを作るところから。
VisualStudioのSP1を入れるところから。

Visual Studio 2008 Service Pack 1 および .NET Framework 3.5 SP1
ここからDLして実行すると、エラーが出てSP1をインストールできなくて困りましたが、3度ほど挑戦してもダメなので、いろいろ思案した揚句、再起動しました。
…問題なくインストール完了。本当、Windowsって不思議ですね。

開発者向け情報 - Microsoft Silverlight
の中段「Visual Studio 2008 SP1 用 Microsoft® Silverlight™ 3 Tools」からSilverlightをダウンロードしてインストール。

この間、3時間。手間取りすぎ。

ソリューションを作成

Visual Studioを立ち上げて、プロジェクトの作成。
プロジェクトの種類で「Silverlight」を選択して、プロジェクト名とソリューション名を入力すると、新しいSilverlightアプリケーションダイアログが出る。
Webプロジェクト名とかいうのも入力する必要があるみたい。とりあえずなんとかかんとか入力する。

できたソリューションファイルを眺めるに、プロジェクト名.Webというのはアプリケーションをデバッグするためのデプロイプロジェクトのような感じがする。

で、肝心のSilverlightのプロジェクトは、App.xamlとMainPage.xamlだけがある。この二つの違いはなんだろう…
ググると、

とかがヒット。そういえばGoogleは先生なのに、なぜYahooは先生じゃないのだろうか。
Gooも先生じゃないな。
これによると、

App.xaml には、アプリケーション全体に適用されるリソースおよびコードを指定します。MainPage.xaml には、Web サイトのページに似たページを定義します。

だそうで。App.xamlがProject.csで、MainPage.xamlがForm1.csなのだと勝手に納得。

ボタンの作成

とりあえず、何をするにもボタンが必要だということで、

<UserControl x:Class="Browser.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot">
    <Button x:Name="start" Click="start_Click"></Button>
  </Grid>
</UserControl>

とかして、実行!
…何も表示されない…と思ったら、GridLayoutのため、ボタンの高さと幅を指定しない場合、画面全体にボタンが表示される見たい。あと、Contentを指定しなかったのでラベルもない。

<Button x:Name="start" Click="start_Click" Content="Start" 
                       Height="30" Width="200"></Button>

とかして、デバッグ。ど真ん中にボタンが表示されました。

Jsonサービス呼び出し

を見るに、clientaccesspolicy.xmlというのをサービス側のドメインルートに置く必要があるみたい。
今回呼び出そうとしているサービスは、
http://tmboarddb.nagao.nuie.nagoya-u.ac.jp:8080/tmboarddb/rpc.json
なので、
http://tmboarddb.nagao.nuie.nagoya-u.ac.jp:8080/clientaccesspolicy.xml
というURLで参照できるようにファイルを設置する。
WindowsでTomcatな場合、場所は、

C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ROOT

みたい。ここに、上のサイトにあった

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

という内容のファイルをclientaccesspolicy.xmlという名前で保存する。しかしいつも思うけど、これってサーバー側じゃなくてクライアント側にも設定しなきゃならないものなんじゃないのかなぁ。まぁいいや。

これでサービス呼び出しの準備は整いました。

ちなみにクロスドメインの設定ができていないと、System.Reflection.TargetInvocationExceptionという例外が飛びます。

System.Reflection.TargetInvocationException: 操作中に例外が発生し、結果が無効になりました。例外の詳細については InnerException を確認してください。 ---> System.Security.SecurityException ---> System.Security.SecurityException: セキュリティ エラーです。

こんなんです。ドメインのルートにclientaccessepolicy.xmlが設置されていてアクセス可能か確かめてください。

HTTPサービスの呼び出し方

を参考にすると、HTTPサービスを呼ぶには、WebClientかHttpWebRequestを使うらしい。いろいろヘッダを指定したい場合は、HttpWebRequest、とりあえずテケトーに使いたい場合は、WebClientでエイヤッ!とすればできるみたいです。
とりあえず、WebClientでやってみます。

WebClient client = new WebClient();

public MainPage()
{
    InitializeComponent();
    client.UploadStringCompleted +=
        new UploadStringCompletedEventHandler(client_UploadStringCompleted);
}

private void start_Click(object sender, RoutedEventArgs e)
{
    this.Call("TimeMachineBoard.getSession", param);
    // Sent the request to the specified URL.
    client.UploadStringAsync(new Uri("http://tmboarddb.nagao.nuie.nagoya-u.ac.jp:8080/tmboarddb/rpc.json",
        UriKind.Absolute), "[30b45ed01725434b941f7ff5f0cbc7b2]");
}

// Event handler for the UploadStringCompleted event.
void client_UploadStringCompleted(object sender,
   UploadStringCompletedEventArgs e)
{
    Console.WriteLine(e.Result);
}

とかですか。でも、ちゃんとJson-RPCのリクエストを送らないと反応してくれません。

Jsonicのサービスを呼び出すためのメソッドを作る
/// <summary>
/// リモートメソッドの呼び出し
/// </summary>
/// <param name="method">メソッド名</param>
/// <param name="param">パラメーター</param>
public void Call(String method, JsonArray param)
{
    String request = this.CreateJsonicRequest(method, param);
    // Sent the request to the specified URL.
    client.UploadStringAsync(new Uri("http://tmboarddb.nagao.nuie.nagoya-u.ac.jp:8080/tmboarddb/rpc.json",
        UriKind.Absolute), request);
}

/// <summary>
/// Jsonicのリクエストを作成する
/// </summary>
/// <param name="method">Jsonicのメソッド名</param>
/// <param name="param">パラメーター</param>
/// <returns>JSON形式のJsonicリクエスト</returns>
protected String CreateJsonicRequest(String method, JsonArray param)
{
    String json = "{\"method\":\"" + method + "\"," +
                  " \"params\":" + param.ToString() + "," +
                  " \"id\":1}";
    return json;
    //Encoding.UTF8.GetString(Encoding.Unicode.GetBytes(json));
}

CreateJsonicRequestで、
JSON-RPC
準拠のJSONIC - simple json encoder/decoder for javaに書かれているリクエストを作ります。
あとは、先ほど出てきたやつと混ぜればちゃんとレスポンスが帰ってきます。

JsonArray param = new JsonArray();
param.Add(new JsonPrimitive("30b45ed01725434b941f7ff5f0cbc7b2"));
this.Call("TimeMachineBoard.getSession", param);

たとえば、こんな感じで呼び出します。

Jsonレスポンスの処理

Jsonのレスポンスを受け取ったら、オブジェクトにパーズしたいですよね。その場合は、

JsonValue result = JsonObject.Parse(e.Result);

みたいにすればいいようです。ただ、モデルクラスにどうやってマッピングするかは考えていません。調べます。

今日はそんなところです。

#追記
調べました。