Silverlightでamazonの書籍検索を使ってみよう
書籍管理用のSilverlightアプリがほしいなーと思っています。画像や書籍の情報なんかを手軽に利用したいとなるとやっぱりAmazonかな、ということで、とりあえずAmazon Web Service API(Amazon AWS)をSilverlightで使ってみようと思います。
1.Amazon AWSのアクセスポリシー
Silverlightではそのアプリケーションがホストされたドメイン以外のサイトに対して通信を行う場合、クロスドメインの制限がかかるため、通信先のホストのルートディレクトリにあるclientaccesspolicy.xmlか、crossdomain.xml でそのサイトに対するアクセスが許可されている必要があります。Silverlightで他のドメインに通信をする場合は、まずはじめにこの部分を確認する必要があります。
くわしくは、CodeZineのこの記事を参照してくださいw。
⇒Silverlightとサーバーサービスの連携
嬉しいことに、日本のamazon のWebサービスがホストされているhttp://ecs.amazonaws.jp/のcrossdomain.xmlを見ると、嬉しいことに次のように全サイトからのアクセスを許可してくれています。
Amazon Web Service の crossdomain.xml (http://ecs.amazonaws.jp/crossdomain.xml)
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-access-from domain="*" /> </cross-domain-policy>
2.Amazon AWS Access Keyの取得と確認
Amazon AWSを利用するためには、Amazon AWSのサイトで、自分のAccess key IDとSecret Access keyを確認する必要があります。Amazon AWSのAccess KeyはこのAmason AWSのポータルサイトで確認できるので、自分のAmazonアカウントでログインして確認して下さい。Access key Idが公開鍵で、Secret Access keyが秘密鍵になります。この2つのキーをもとにAWSを使うための署名を作成します。Secret Access keyは外部に漏れないように厳重に管理しましょう。
Amazon AWSのポータルサイト⇒http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key
Amazon AWSに登録していない人や、Amazonのアカウントを持っていない人やはこのあたりのサイトを参考にAmazon AWSのアカウントを取得してください。
3.書籍検索クエリの作成
Amazon AWSでは書籍の検索に限らず、Amazonで取り扱っている商品の検索や購入をProduct Advertising APIを使うことで行うことができます。今回はとりあえずSilverlightアプリケーションで書籍の検索を行ってみましょう。
書籍の検索は、次のクエリを利用します。(実際は1行です。)
http://ecs.amazonaws.jp/onca/xml?
AWSAccessKeyId=xxx&
Keywords=Silverlight&
Operation=ItemSearch&
ResponseGroup=Small&
SearchIndex=Books&
Service=AWSECommerceService&
Timestamp=2010-12-23T15%3A54%3A24Z&
Version=2009-03-31&
Signature=xxxxx"
リクエストの詳細はこのあたりで確認できます。クエリの詳細はこのあたりを確認して下さい。
⇒ Ajax Tower.jp 商品検索(ItemSearch)
また、Amazon AWSへのリクエスト2009年に改正され、リクエスト中にタイムスタンプや利用者のハッシュした署名を含める必要があります。署名の作成方法は下記のサイトで確認できます。
⇒ Ajax Tower.jp 認証(Timestamp及びSignature)
署名を作成するのは大変ですが、.NETで署名を作成するライブラリが、Amazonのデベロッパーフォーラムで公開されていますので、通常のパッケージに含まれるSignedRequestHelperクラスを使うのがいいでしょう。
⇒ Product Advertising API Signed Requests Sample Code – C# REST/QUERY
利用方法はこんな感じです。
var helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION); var param = new Dictionary<string, String>(); param["Service"] = "AWSECommerceService"; param["Version"] = "2009-03-31"; param["Operation"] = "ItemSearch"; param["Keywords"] = bookName.Text; param["SearchIndex"] = "Books"; param["ResponseGroup"] = "Small"; var requestUrl = helper.Sign(param); var request = new WebClient(); request.DownloadStringCompleted += (_s, _e) => { if (_e.Error != null) { MessageBox.Show(_e.Error.ToString()); return; } MessageBox.Show(e.Result); }; request.DownloadStringAsync(new Uri(requestUrl)); }
MY_AWS_ACCESS_KEY_IDとMY_AWS_SECRET_KEYには2で確認したキーを指定します。DESTINATIONには日本のAWSを利用するのでJPを指定します。
4.SignedRequestHelperの変更
困ったことに、SignedRequestHelperは.NET Frameworkを対象に書かれているため、Silverlightでは利用できないクラスを使っています。具体的には、SortedDictionaryクラスがそれです。特に、AWSのクエリはハッシュの作成のために、クエリの順番が特に重要なためです。しかし落胆することはありません、問題はキーの順番が保証されてさえいればいいのです。嬉しいことにSilverlightにはLINQがあります。とりあえず順序保証がされないDictonaryにパラメータを放りこんで、ハッシュを作成する時に並び替えてあげればいいのです。
/* * Consttuct the canonical query string from the sorted parameter map. */ private string ConstructCanonicalQueryString(IDictionary<string, string> sortedParamMap) { StringBuilder builder = new StringBuilder(); if (sortedParamMap.Count == 0) { builder.Append(""); return builder.ToString(); } // Dictionaryは順序保証されていないので、OrderByしてあげる // foreach (KeyValuePair<string, string> kvp in sortedParamMap) foreach (KeyValuePair<string, string> kvp in sortedParamMap.OrderBy(item => item.Key)) { builder.Append(this.PercentEncodeRfc3986(kvp.Key)); builder.Append("="); builder.Append(this.PercentEncodeRfc3986(kvp.Value)); builder.Append("&"); } string canonicalString = builder.ToString(); canonicalString = canonicalString.Substring(0, canonicalString.Length - 1); return canonicalString; }
4.XMLをパースする。
リクエストに対するレスポンスはこんなXMLです。
XMLのパースといえば、やっぱりここでも LINQ です。 DataGridに表示するためには、匿名型ではダメなので、検索結果というクラスを作って、パース結果をそのクラスに流し込んでいます。
// Grid表示 var xml = XElement.Parse(_e.Result); XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2009-03-31"; var result = from item in xml.Descendants(ns + "Item") select new 検索結果 { Asin = item.Element(ns + "ASIN").Value, Url = item.Element(ns + "DetailPageURL").Value, Author = item.Element(ns + "ItemAttributes").Element(ns + "Author").Value, Title = item.Element(ns + "ItemAttributes").Element(ns + "Title").Value, }; bookList.ItemsSource = result.ToList();
5.動かしてみる。
うごいた。後は受け取ったURLに含まれる情報からAmazonのサイトをWebブラウザーコントロールに表示市たいんですが、それはCodeZineの連載を見てください。多分2月ぐらいの回を乞うご期待です。
6.おまけ
とりあえずサンプルプロジェクトはここにおいておきます。
http://cid-b17f5fcd8095628b.office.live.com/self.aspx/%E5%85%AC%E9%96%8B/WebApplication2.zip
#追記
通りすがりさんのコメントを参考に、ConstructCanonicalQueryStringメソッドでのソート時に、大文字小文字を区別してソートするようにしました。(2010/12/30)
参考にさせていただきました。
アフィリエイトを使用する場合はリクエストに
AssociateTag=[アソシエイトID]
を追加してあげます。
ただしこのAssociateTagが曲者でして・・・
SignedRequestHelperでソートをかけられた時に、SortedDictionaryを使った際には
AWSAccessKeyId → AssociateTag
の順になります。
(大文字優先のアルファベット順)
ところがSilverlight対応のためLINQのOrderByを使った際には
AssociateTag → AWSAccessKeyId
の順になります。
(大文字・小文字に関係なくアルファベット順)
そしてamazon APIの仕様としては前者(AWSAccessKeyId → AssociateTag)の並び順でないとリクエストを受け付けてくれないのです。
実際、こちらで公開していただいた改修済みのSignedRequestHelperでは、AssociateTagを含めたリクエストは正常に行えません。
というわけで何か良い案がないかと検討している最中です。
もしAssociateTagも加味したSignedRequestHelperを改めて公開していただけるのであれば、ありがたい限りです・・・。
ではでは。
自己解決しました(笑
良く見たらSignedRequestHelperの中にもともと
class ParamComparer : IComparer
が定義されていましたね。
なので
こちらで修正&公開していただいているConstructCanonicalQueryStringメソッドの下記部分を・・・
————-
foreach (KeyValuePair kvp in sortedParamMap.OrderBy(item => item.Key))
{
~
————-
このように直してあげれば・・・
————-
ParamComparer pc = new ParamComparer();
foreach (KeyValuePair kvp in sortedParamMap.OrderBy(item => item.Key, pc))
{
~
————-
AssociateTagを含めたリクエストも正常に送れるようになりました。
いやはや、良く確認もせずコメントすべきではないですね(笑
お騒がせいたしました。
参考になればと思います。
ではでは。
ありがとうございます。
参考にさせていただき、あらためてSkyDriveのファイルを修正して公開しました。