技術日記
SilverlightでのWeb APIアクセス処理(無駄な試行錯誤の過程)Edit

なんかもう、Silverlightの普通の.NET Frameworkとの互換性のなさにはうんざりしているんだけど、その中でも特にうんざりしたWebRequest周りについて。

普通に.NET FrameworkでWeb APIとかにアクセスしたい場合、System.Net.WebClientとかを使うと簡単にできる。

[sourcecode language="c#"]
var client = new System.Net.WebClient();
var url = "http://example.com/path/to/api";
var values = new NameValueCollection(){{"key", "value"}};
var byteResult = client.UploadValues(url, values);
var stringResult = Encoding.UTF8.GetString(byteString);
[/sourcecode]
受け取った文字列がJSONだった場合は、
[sourcecode language="c#"]
var serializer = new JavaScriptSerializer();
var result = serializer.Deserialize(stringResult, typeof(ObjectForMapping);
[/sourcecode]
みたいな感じで受け取り用のクラス(ObjectForMapping)を用意しておいて、それにデシリアライズしたりとか。 あと、これだけだとCookieベースの認証とかが維持できないんで、webclient 継承 cookiecontainerでググると出てくるような感じで、
[sourcecode language="c#"]
class MyClient: WebClient
{
  protected CookieContainer _cookie = new CookieContainer();
  protected override GetWebRequest(Uri address)
  {
    var request = base.GetWebRequest(address);
    if (request is HttpWebRequest) {
      (request as HttpWebRequest).CookieContainer = _cookieContainer;
    }
    return request;
  }
} 
[/sourcecode]
みたいな感じでCookieを扱えるクラスを作っておくと、自動的にCookieを維持してくれるようになる。 でまあ、Silverlightでも同じようなことができるよね、と思って、クラスライブラリの名前空間とかファイルとかの構成がいろいろ変わっているのを乗り越えて、
[sourcecode language="c#"] 
using System.Windows.Browser; 
using System.Net.Browser; 
class MyClient: WebClient  
{
} 
[/sourcecode]
とやったところ、これはコンパイル自体は通るくせに、new MyClient()するコードブロックを通ったとたんに、何のエラーメッセージも出さずにそっと落ちる。 どうやらWebClientを継承したらだめなようだ。

けど、そんなドキュメント、どこにも見つからないし、だいたいコンパイルは通ってるんだけどなー。っつーか、結局WebClientを継承するだけでもだめだ、と気づくまでどれだけ時間がかかったことか。 しょうがないんで、自前でWebRequestを使って処理を行うような構成に変更することにする。必要に応じて、

[sourcecode language="c#"]
var request = WebRequest.Create(new Uri(url));
(request as HttpWebRequest).CookieContainer = _cookie; // 使い回すCookieContainer
request.BegubGetResponse(callback, request);
[/sourcecode]
みたいな感じね。でも、これが通らない。なぜかというと、
[sourcecode language="c#"]
var request = WebRequest.Create(new Uri(url));
[/sourcecode]
で返されるWebRequestがHttpWebRequestではなくBrowserHttpWebRequestってやつになっている。なんじゃそりゃ。それはどの辺のドキュメントに書いてあるんだよ。MSDN内で検索してもそのドキュメントが見つからないぞ。

いろいろ探し回った結果、方法: Cookie の取得と設定を行うというドキュメントを見つけた。解説自体は今までやっていたことを説明しているだけで、特に新しい情報はない。しかし、サンプルコードに見慣れない処理が入っている具体的には、

[sourcecode language="c#"]
IWebRequestCreate creator = WebRequestCreator.ClientHttp;
WebRequest.RegisterPrefix("http://", creator);
WebRequest.RegisterPrefix("https://", creator);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://api.search.live.net/clientaccesspolicy.xml")
[/sourcecode]
なんじゃこりゃ。WebRequestCreatorクラスのドキュメントを見てみると、
Silverlight では、ブラウザーとクライアントのどちらで Silverlight ベース アプリケーションの HTTP 処理を行うかを指定できます。既定では HTTP 処理はブラウザーによって行われるため、クライアントで HTTP 処理を行う場合は選択の必要があります。通常、HTTP の要求と応答の処理方法を指定するには、このクラスのプロパティを WebRequest の RegisterPrefix メソッドSilverlight では、ブラウザーとクライアントのどちらで Silverlight ベース アプリケーションの HTTP 処理を行うかを指定できます。既定では HTTP 処理はブラウザーによって行われるため、クライアントで HTTP 処理を行う場合は選択の必要があります。通常、HTTP の要求と応答の処理方法を指定するには、このクラスのプロパティを WebRequest の RegisterPrefix メソッドに渡します。に渡します。
なるほど、Silverlightってそこまでブラウザと密結合していたのね。 標準ではBrowserHttpWebRequestってのが使われるから、非Silverlight環境と互換性のあるHttpWebRequestを使いたい場合は、サンプル通りにSilverlight側でhttpスキームを処理するように指定してからWebRequest.Createしなければならないのか。そうやったらちゃんとHttpWebRequestが生成されるようになった。CookieContainerも使えている。

で、一通りコンパイルは通るようになったし、実行もできるようになったんだけど、なぜかCookieによる認証が維持されない。認証CookieがHttpOnlyなんで、中身をのぞけないからよくわからないが、少なくとも同じようなコードで通常の.NET Frameworkでは動作するものが、Silverlight環境ではうまくいかない。

うがー、どないなっとんねん。と、SilverlightのHTTP通信周りのドキュメントをもう一度良い返してみる。そうしたら、Silverlightの標準の方のBrowserHttpWebRequestでは、ブラウザの機能を使ってHTTPリクエストを処理するんで、ブラウザ側のCookieがそのまま使えるっぽい? なんか非Silverlight用に作ったコードを移植しようとしていたのは無駄な努力だった?

というわけで、単純に認証Cookieを保持したいだけだったら、わざわざHttpWebRequestを生成して自前でCookieContainerを処理したりする必要はなく、何も考えずにSilverlight標準のWebアクセスクラスを使ってしまえば、ブラウザ相当のCookie処理が使われてくれる。

先に非Silverlight用のWeb APIクライアントコードを書いて、それをSilverlightでも動くように移植しようとしたせいで、無駄にはまったんだね。はじめからSilverlight用に書いていれば何も考えなくても動くものが作れた気がする。ただし、ブラウザ互換のWebアクセスクラスにも制約があるんで、それで全部いけるわけではないけど。

書き忘れていたけど、クロスドメインアクセス用のcrossdomain.xmlとかは、Flashとかと同様、サーバーサイドにあらかじめ用意しておく必要がある。

で、APIアクセスライブラリとしては一通り動くようになったから、SilverlightアプリケーションからWeb APIをコールして、その結果をTextBoxに表示するというテストアプリを作ってみた。が、「System.UnauthorizedAccessException: 無効なスレッド間アクセスです。」というエラーが出て表示できない。

どうやらAsyncCallbackから呼び出されるスレッドと、UI部品などを管理するメインスレッドとは別スレッドだから、直接は呼び出せないと言うことらしい。

いろいろググったところ、Silverlight 奮戦記 ファイルを読む β2 対応版という情報を見つけた。AsyncCallbackから呼び出されたメソッドから、さらにSyncronizationContect.Current.Post()を使ってメインスレッドとの同期を取ってから、メインスレッド側のパーツに対する処理を行えば回避できるみたいだ。でも、この辺の情報は公式ドキュメントではどの辺に書いてあるのかなー。

といった感じで、.NET Frameworkがらみのドキュメントは非常にわかりにくいし、同じ名前のクラスが多いくせに制限事項が違うSilverlight環境の場合は、それがさらにひどくなっているんで、あまり深入りしたくない気持ち満点になった。デバッグ環境としてできがいいVisualStudioなのに、Silverlightのデバッグだとうまく動かないことがあまりにも多いし。

Published At2011-12-09 21:43Updated At2011-12-09 21:43