2007年4月25日水曜日

ASP.NET AJAXで他ドメインのWebサービスを呼び出す part1

AJAXは便利なんですが、XMLHttpRequestのセキュリティ制限により、他ドメインの呼び出しはできません。これを回避する技は(JSONPなど)いくつかありますが、ASP.NET AJAXにはそれらを簡単に扱う道具は用意されていません。というか、ASP.NET AJAX Futuresには、IFrameを利用して他ドメインを呼び出すためのライブラリが含まれていたのですが、現在のFutures CTPからは除かれています。セキュリティのためにも、その手の機構には慎重になるべきですね。


ということで、ASP.NET AJAXで他ドメインを呼び出すのであれば、自ドメイン上に置いたXML Webサービスを通して呼び出すしかありません。呼び出したいのが他ドメインのXML Webサービス(要するにSOAP)であれば、あまりコードを書くこともなく実現できますが、REST APIとかXML-RPCだったらどうでしょうか。


……と思っていたら、ASP.NET AJAX Futuresに便利なものがありました。それが、asbxファイルです。



asbxファイルは、他ドメインのサービスへの橋渡し(bridge)をするクラスを作り出します。しかも、GETメソッドを使うREST APIであれば結構簡単に書けます。


サンプルを書く前に注意。Web.configは、Futures CTPを扱えるようにしておいてください。ふつうにインストールをすると、asbxファイルを扱うための設定が間違っているので、<system.web>要素の中の以下の部分を修正してください。



<compilation>
<buildProviders>
<-- アスタリスクは不要
<add extension="*.asbx" type="Microsoft.Web.Preview.Services.BridgeBuildProvider"/>
-->
<add extension=".asbx" type="Microsoft.Web.Preview.Services.BridgeBuildProvider"/>
</buildProviders>
<compilation>


さて、Web.configの設定が終わったところで、サンプルとして呼び出すのは……( AWS を使うことが多いんですが登録が必要なので今回はやめて )……Livedoorのお天気Webサービスにしましょう。


まずは仕様を確認しましょう。呼び出しURLは http://weather.livedoor.com/forecast/webservice/rest/v1 です。パラメータは2つ、cityとdayです。これらをURLクエリパラメータとして付加し、HTTP GETで取得します。さてこれをasbxで書いてみましょう。ファイル名はとりあえず、lwws.asbxとでもしましょうか。





<?xml version="1.0" encoding="utf-8" ?>
<bridge namespace="MyNamespace" className="MyClass">
<proxy type="Microsoft.Web.Preview.Services.BridgeRestProxy"
serviceUrl="http://weather.livedoor.com/forecast/webservice/rest/v1" />
<method name="getWeather">
<input>
<parameter name="city" />
<parameter name="day" />
</input>
</method>
</bridge>


これだけでも動かせます!



asbxファイルをこのように書くとどうなるかというと……次のXML Webサービスみたいなものができたのと同様に扱えます。



namespace MyNamespace
{
[WebService]
[ScriptService]
public class MyClass : System.Web.Services.WebService
{
[WebMethod]
public string getWeather(object args)
{
// Livedoorお天気Webサービスを呼び出すコード
}
}
}



よって、ScriptManagerのServicesプロパティでlwws.asbxを指定してやれば、JavaScriptからは、



MyNamespace.MyClass.getWeather(args, succeeded);

といったスクリプトで呼び出すことができます(注:succeededは呼び出し成功時のコールバック関数)。




さてここで疑問が2つ浮かびます。そうです。「引数は?」「戻り値は?」という疑問です。


まずは引数から。JavaScriptではこんな感じで書きます。



var args = new Object();
args.city = 63; // 東京地方を指定。サービス仕様を確認のこと。
args.day = 'tomorrow'; // 明日の予報を指定。サービス仕様を確認のこと。
MyNamespace.MyClass.getWeather(args, succeeded);

あるいはこう書いてもOKです。



var args = { city: 63, day: 'tomorrow' }
MyNamespace.MyClass.getWeather(args, succeeded);

詳細は省略しますが、すべてのオブジェクトは連想配列であると知っていれば、結果が同じであることは分かるでしょう。




次は戻り値です。ここは少し長くなりますよ。


上で書いたXML Webサービスのメソッドシグネチャを見返してください。戻り値はstringになっていますね。そうです。お天気Webサービスの結果として得られるXMLがそのまま文字列として返されます。長くなるので、http://weather.livedoor.com/forecast/webservice/rest/v1?city=63&day=tomorrow で実物を見てください。



もちろん、この文字列をもとにDOMオブジェクトを作って、必要な部分を操作することもできますが、面倒ですよね。面倒だといってください。そんな人のために、結果をサーバ側で変換して、扱いやすい形式にすることができます。


Futures CTPには、Microsoft.Web.Preview.Services.IBridgeResponseTransformerインタフェースと、それを実装するクラスが4種類含まれています。今回はそれらの実装クラスの中から、 Microsoft.Web.Preview.Services.XPathBridgeTransformer クラスを説明しましょう。


XPathBridgeTransformerクラスの使い方ですが、これもasbxファイルに記述します。たとえばこんな感じ。





<?xml version="1.0" encoding="utf-8" ?>
<bridge namespace="MyNamespace" className="MyClass">
<proxy type="Microsoft.Web.Preview.Services.BridgeRestProxy"
serviceUrl="http://weather.livedoor.com/forecast/webservice/rest/v1" />
<method name="getWeather">
<input>
<parameter name="city" />
<parameter name="day" />
</input>
<transforms>
<transform type="Microsoft.Web.Preview.Services.XPathBridgeTransformer">
<data>
<attribute name="selector" value="/lwws" />
<dictionary name="selectedNodes">
<item name="Area" value="location/@area" />
<item name="Prefecture" value="location/@pref" />
<item name="City" value="location/@city" />
<item name="DateTime" value="forecastdate" />
<item name="Forecast" value="telop" />
<item name="Description" value="description" />
<item name="MaxTemperature" value="temperature/max/celsius" />
<item name="MinTemperature" value="temperature/min/celsius" />
</dictionary>
</data>
</transform>
</transforms>
</method>
</bridge>



こんな風に書くと、JavaScriptでは戻り値にこんな風にアクセスできます。





function succeeded(result) {
var area = result[0].Area;
var pref = result[0].Prefecture;
var city = result[0].City;
var datetime = result[0].DateTime;
var forecast = result[0].Forecast;
var desc = result[0].Description;
var max = result[0].MaxTemperature;
var min = result[0].MinTemperature;
}



要は、戻り値がオブジェクト(連想配列)の1次元配列になります。




なぜこうなるのか?というと、XPathBridgeTransformerクラスは、XPathで指定された要素を抜き出してくれるからです。asbxファイルの記述を見ながら説明しましょう。




まず見るべきなのは<attribute>要素です。name="selector"はXPathBridgeTransformerクラスのためのおまじないです。注目すべきはvalue属性で、ここに1次元配列として抽出する要素を表現するXPathを書きます。ここでは"/lwws"を指定しているので、ルート要素である<lwws>を抜き出します。(なので配列の要素数は1です。)


続いて<dictionary>要素です。name="selectedNodes"もXPathBridgeTransformerクラスのためのおまじないです。その子要素である<item>要素で、連想配列に格納する値を指定します。たとえば最初の記述は<item name="Area" value="location/@area" />ですね。これで、ルートの<lwws>要素直下にある<location>要素のarea属性の値を抜き出し、Areaというキーで連想配列に追加します。以下も同様ですので説明は省略します。


というわけで、結果は連想配列の1次元配列となるわけです。ちなみに連想配列の値は文字列型となっています。数値型や日付型やブール型を期待すると痛い目にあいます。

0 件のコメント:

コメントを投稿