はじめに
Silverlightはブラウザにダウンロードして動作するアプリケーションです。
つまり業務アプリケーションでメインとなる、データの更新や検索、他システムとの連携といった処理を記述するためには、Silverlightとサーバーサービスとの間で通信を行い、データをやりとりする必要があります。
Silverlight 1.0ではJavaScriptしか利用できなかったため、AJAXでよく利用されるXMLHttpRequest
オブジェクトを使用してサーバとの通信を行っていました。Silverlight 2.0(以降、断りのない限り「Silverlight」と表記)では.NET Frameworkのサブセットとして動作するため、WebClient
やHttpRequest
/HttpResponse
、Socket
クラスといったネットワークAPIを利用してサーバーサービスとの連携プログラムを簡単に作成することができます。
今回はSilverlightとサーバーサービスの連携を行うための基礎について解説します。
対象読者
Silverlight開発初心者。
SilverlightのネットワークAPI
最初に述べたように、Silverlightでは.NET Frameworkの各種クラスを使用してネットワークプログラムを記述することができます。
Silverlightでは、次の方法でサーバーサービスとの連携を行うことができます。
- ASP.NETサービスリファレンスの利用
- System.Net.WebClientの利用
- System.Net.HttpRequest/HttpResponseの利用
- System.Net.Socketの利用
これらはSilverlightでも.NET Frameworkのサブセットとして定義されています。
動作はほぼ同じですが、Silverlightではサポートされていないメソッドがあるため、MSDNライブラリで詳しい仕様を確認してください。
ASP.NETサービスリファレンスの利用
ASP.NETサービスリファレンスは、WSDLというWebサービスの定義情報から.NETのクラスで利用するためのプロキシクラスを作成して、Webサービスを呼び出す方法です。
SilverlightでもVisual Studioを利用することで、簡単にプロキシクラスを作成してWebサービスを呼び出すことができます。
それでは、Visual Studioから新しいSilverlightアプリケーションを作成し、SilverlightをホストしているWebSiteにWebServiceを追加してみましょう。
1.プロジェクトの作成
まずはSilverlightとSilverlightをホストするためのASP.NETのプロジェクトを作成します。
Visual Studioメニューの[ファイル]-[新規作成]-[プロジェクト]から[Silverlight]を選択し、Silverlightアプリケーションを作成します。その際、プロジェクト名はSilverlightDemoとします。
[OK]ボタンをクリックすると、SilverlightをホストするためのASP.NETページを作成するか訪ねられるので、そのまま[OK]をクリックしてASP.NETページを作成してください。
2.Webサービスの作成
次にSilverlightから呼び出すためのWebサービスを作成します。
作成されたSilverlightDemo.Webプロジェクトを右クリックして[追加]-[新しい項目]から[WebService]を選択します。
Webサービスを作成すると、Visual Studioが自動生成したHello Worldという文字列を返却するHelloWorld
メソッドが定義されていますので、今回はこのサンプルを利用します。
3.SilverlightからWebサービスの参照
Silverlightから「2.」で作成したWebサービスを呼び出すために、サービスリファレンスを作成します。
SilverlightDemoプロジェクトを右クリックし、サービス参照の追加から「2.」で作成したWebサービスを参照します。
今回は、SilverightをホストするASP.NETと同じアプリケーションでWebサービスをホストするため、[探索]ボタンをクリックすることで「2.」で作成したWebService1を検索できます。
ここで、「アドレスからメタデータをダウンロードするときに、エラーが発生しました。有効なアドレスを入力していることを確認してください。」といったメッセージが表示された場合は、Silverlightで参照する前に一度WebサービスをVisual Studioからデバック実行して見てください。
また、Silverlightをホストするドメイン以外のWebサービスを参照する場合は、後述するクロスドメインの制限がありますので注意してください。
4.Webサービスの呼び出し
「3.」で作成したWebサービスのプロキシクラスからWebService1のHelloWorld
メソッドを呼び出します。
SilverlightプロジェクトのPage.xamlのXAMLにWebサービスを呼び出すためのボタンを記述します([リスト1])。
<UserControl x:Class="SilverlightDemo.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="White"> <Button Height="30" Margin="100,100,100,0" Content="サービスの呼び出し" Click="Button_Click"/> </Grid> </UserControl>
これでWebサービスを呼び出すための準備が終わったので、C#から作成したWebサービスを呼び出してみましょう。
Page.xaml.csを開き、Page
クラスにボタンクリックのイベントハンドラを作成します([リスト2])。
private void Button_Click(object sender, RoutedEventArgs e) { // (1) Webサービスプロキシの作成 var service = new ServiceReference1.WebService1SoapClient(); // (2) 呼び出し終了時のイベントハンドラの設定 service.HelloWorldCompleted += (serviceObject, serviceEvent)=> { if (serviceEvent.Error != null) MessageBox.Show("通信エラー:" + serviceEvent.Error.ToString()); else MessageBox.Show(serviceEvent.Result); }; // (3) Webサービスの呼び出し service.HelloWorldAsync(); }
Silverlightアプリケーションでは、Webサービスの呼び出しは非同期(Async)のみサポートされています。このためSilverlightでWebサービスを呼び出す場合は次の手順でサービスを呼び出す必要があります。
- Webサービスプロキシの作成
- 呼び出し終了時のイベントハンドラの設定
- Webサービスの呼び出し
これでWebサービスを呼び出すためのコードが記述できたので、Visual Studioメニューの[デバック]-[デバックの開始]から[Silverlight]を起動してみましょう。
起動すると次のような画面が表示されるので、サービスの呼び出しボタンをクリックしてください。メッセージボックスが表示され、HelloWorldが表示されるはずです。
自動生成されたプロキシクラスを使うことで、ネットワークプログラムやXMLからのデータの読み出しを意識せず、静的に型付けされたコードからサーバーサービスを呼び出すことができます。
サーバー側をASP.NETで作成する場合、Silverlightから呼び出すサービスをASP.NETWebサービス/ASP.NET WCFで作成し、サービスリファレンスによる参照を行うことで安全に、簡単にアプリケーションを作成することができます。
ASP.NET Webサービスを呼び出す際の注意点
ここまででSilverlightからWebサービスを呼び出すことが可能になりました。
SilverlightからWebサービスを呼び出す際にはいくつかの注意点がありますので、ここで触れておきましょう。
WebServiceの設定情報
サンプルではVisual Studioのテスト用Webサーバー上にWebサービスを配置しましたが、Silverlightを本番環境に移送する場合は、Webサービスの参照も本番のWebサービスに切り替える必要があります。
WindowsアプリケーションやASP.NETではapp.configやweb.configといった設定ファイルにWebサービスへの参照情報を記述しましたが、Silverlightでもサービスリファレンスの設定時に作成されるServiceReference.ClientConfigファイルにWebサービスへの参照情報が記述されています。
本番環境に移送する場合には、このファイルのendpoint情報を変更しSilverlightを再パッケージ(XAP)化する必要があります。このため実際の開発時にはテスト用と本番用の2つのServiceReference.ClientConfigを用意したり、MSBuildのビルド時に定義を置き換えたりといった工夫が必要になると思います。
MSBuildのビルド時にXMLの定義情報を書き換える方法がNAndu氏の記事『XmlMassUpdateタスクを使用したデプロイ』にまとまっています。ぜひ参考にしてください。
クロスドメインリクエスト
もう一つ大きな問題になるのが、Silverlightから違うドメインのサービスを呼び出す際に問題になるクロスドメインリクエストの問題です。
Silverlightの既定ではSilverlightをホストしているドメインにあるサービス以外へのリクエストは禁止されています。もしSilverlightからの接続が許可されていない他ドメインにあるWebサービスを呼び出そうとした場合、実行時にCommunicationExceptionの例外が発生します。
他ドメインに対するリクエストを無制限に許してしまうと、悪意を持ったプログラムは、利用者が気づかないうちに他ドメインのサービスに対し、サービス拒否攻撃(DDoS)やDNS Rebinding、逆トンネルといった攻撃を行ってしまう可能性があります。
Silverlightでは、Webサービスをホストするドメインのサーバーで、Silverlightからのリクエストを許可することを表すドメイン間ポリシーファイルをサーバーのルートサイトに公開してもらうことで、クロスドメイン間のリクエストが許可される設計になっています。
つまり業務で他システムのサービスを利用する場合、対象サービスのルートサイトにドメイン間ポリシーファイルが提供されているか、提供してもらうことが可能かを確認・検討することが必要となります。
また、Visual StudioのテストWebサーバーでSilverlightをホストしている場合、「http://localhost:2591」といったようにポートが自動的に割り振られます。この状態で、「http://localhost」に存在するWebサービスに対してリクエストを発行すると、上記のCommunicationExceptionが発生してしまいます。
この場合、localhostのルートサイト(既定ではC:\Inetpub\wwwroot)に後述するドメイン間ポリシーファイルを配置する必要があります。
Visual StudioのテストWebサーバーがバインドするポート番号は次の方法で確認・設定を行うことができます。Visual Studioのソリューションエクスプローラから、該当Webサイトプロジェクトを右クリックして、[プロパティ]-[Web(ポート番号の確認)]を開いてください。
ドメイン間ポリシーファイル
Silverlightで使用されるドメイン間ポリシーファイルは、Flashで使用されているcrossdomain.xmlとSilverlightで新たに採用されたclientaccesspolicy.xmlの2種類があります(Silverlightではcrossdomain.xmlはすべてのドメインに対しアクセスを許可する/しないの2択しか設定できません)。
Silverlightではクロスドメインへのリクエストがあった場合、まず始めに対象ホストのルートサイトからclientaccesspolicy.xmlを検索します。clientaccesspolicy.xmlが存在しない場合は続いてcrossdomain.xmlを検索し、発見されたドメイン間ポリシーファイルを元にアクセス制御を行います。読み込まれたドメイン間ポリシーファイルの設定はブラウザセッションの間有効になります。テスト中にドメイン間ポリシーファイルを書き換えても、ブラウザを再起動するまで変更が有効になりませんので注意してください。[リスト3]にclientaccesspolicy.xmlの例を示します。
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy > <allow-from http-request-headers="SOAPAction"> <domain uri="http://localhost:2591"/> </allow-from> <grant-to> <resource path="/webservice1/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
この例では、「http://localhost:2591」ドメインからのSOAPActionについて、webservice1ディレクトリにあるWebサービスについてアクセスが許可されています。
また、例えばすべてのドメインから、すべての要求に応えるためには[リスト4]のように構成を変更します。
<?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を配置して動作を確かめてください。問題なくサービスを呼び出すことはできましたか?
ASP.NETサービス以外との連携
Silverlightでは、前述のサービスリファレンスだけでなく、System.Net.WebClient
やSystem.Net.HttpRequest
/HttpResponse
を使ったHTTPの通信もサポートされています。
例えば、楽天WEB SERVICEでは、登録した開発者が楽天のサービスをプログラムから呼び出せるように、さまざまなサービスAPIが公開されています。今回は楽天のAPIを利用してSilverlightから商品の検索を行うアプリケーションの作成を行います。
楽天のWebサービスは、http://dynamic.rakuten.co.jp上にホストされており、すべてのドメインからの応答に答えられるようなcrossdomain.xmlが配置されているため、Silverlightから直接Webサービスを呼び出すことができます。
APIの確認と作成するリクエスト文字列
今回は楽天のAPIから商品検索APIを使用して、ユーザーが指定したキーワードで楽天の商品を検索するアプリケーションを作成します。
提供APIのページでAPI仕様を確認すると、商品検索を行うためには表1の4項目が必須であることが確認できます。
No | パラメータ名 | 説明 | 設定値 |
1 | developerId | 開発者固有のID | (楽天WEB SERVICEサイトで取得) |
2 | operation | APIの操作名 | ItemSearch |
3 | keyword | UTF8でエンコードした検索キーワード | 任意 |
4 | version | APIバージョン | 2009-02-03 |
例えば、Silverlightという検索キーワードで検索を行う場合、[リスト5]のようなリクエストを発行します(developerIdはユーザーごとに取得した物を使用してください)。
http://api.rakuten.co.jp/rws/1.13/rest?developerId=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&operation=ItemSearch&keyword=Silverlight&version=2009-02-03"
Webブラウザで上記URLをリクエストすると、検索結果のXMLが返されることが確認できると思います。
画面の作成
検索を行い結果を表示するための画面を作成していきましょう。
今回は、検索エリアに検索を行うテキストボックスと、検索を実行するボタンを配置し、結果エリアに一覧を表示するためのデータグリッドを配置します。画面の完成イメージは次のようになります。
今回はDataGridを使用するので、Visual StudioからSystem.Windows.Controls.Data
のアセンブリを参照しておきましょう。
次に画面のデザインを行います。Visual StudioのソリューションエクスプローラからPage.xamlを右クリックして、[ExpressionBlendで開く]を選択してください。
検索用のテキストボックスとボタンを配置します。
ツールボックスからテキストボックスとボタンを画面に配置し、テキストボックスに「txtSearch」という名前を設定して、ボタンのイベントハンドラに「Button_Click」を設定します。
データグリッドは標準ではExpression Blendのツールボックスに表示されていないので、アセットライブラリからDataGridを選択してツールボックスにDataGridを追加します。
データグリッドに「商品一覧」という名前を付けます。
<UserControl x:Class="SilverlightDemo.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White"> <TextBox Height="30" HorizontalAlignment="Left" Margin="8,20,0,0" VerticalAlignment="Top" Width="150" Text="" TextWrapping="Wrap" x:Name="txtSearch"/> <Button Height="30" Margin="200,20,120,0" VerticalAlignment="Top" Content="検索" Click="Button_Click"/> <Controls:DataGrid Margin="8,70,8,26" x:Name="商品一覧"/> </Grid> </UserControl>
検索ロジックの作成
次に、ボタンクリックのイベントハンドラを実装して、実際に楽天WEB SERVICEを呼び出してみましょう。
SilverlightでHTTPリクエストを行う際には、WebClient
クラスか、HttpRequest
/HttpResponse
クラスを用いてリクエストを行うことができます。ここでは、WebClient
クラスのDownloadStringAsync
メソッドを利用してリクエストを行います。
[リスト7]は[リスト5]で行ったリクエストをプログラムで記述したものです。
private void Button_Click(object sender, RoutedEventArgs e) { // (1).リクエストURLの組み立て var apiAddress = "http://api.rakuten.co.jp/rws/1.13/rest"; var developerId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; var operation = "ItemSearch"; var keyword = HttpUtility.UrlEncode(txtSearch.Text); var version = "2009-02-03"; var requestUri = string.Format( "{0}?developerId={1}&operation={2}&keyword={3}&version={4}", apiAddress, developerId, operation, keyword, version); var client = new WebClient(); // (2).ダウンロード完了時のイベントハンドラを登録 client.DownloadStringCompleted += RakutenDownloadStringCompleted; // (3).サービスのリクエスト client.DownloadStringAsync(new Uri(requestUri), null); } // (4).リクエスト内容の表示 private void RakutenDownloadStringCompleted( object sender, DownloadStringCompletedEventArgs e) { if (webEvent.Error != null) MessageBox.Show("通信エラー:" + webEvent.Error); else MessageBox.Show("結果:" + webEvent.Result); }
このプログラムは次の順序で動作します。
- リクエストURLの組み立て
- ダウンロード完了時のイベントハンドラを登録
- サービスのリクエスト
- リクエスト内容の表示
SilverlightのWebClient
クラスでもサービスリファレンスと同じように非同期リクエストのみのサポートになるため、リクエストの前に、ページの読み込みが終了タイミングで動作するイベントハンドラを登録してから、DownloadStringAsync
でGETリクエストを開始します。サービスリファレンスと同様にリクエストするURLが許可されていない場合はSystem.Security.SecurityException
が発生します。
[リスト8]は「4. リクエスト内容の表示」の部分を変更して、データグリッドに結果を表示するように変更した例です。
public class 商品 { public string 商品名 { get; set; } public string 価格 { get; set; } public string URL { get; set; } } // 4.リクエスト内容の表示 private void RakutenDownloadStringCompleted( object sender, DownloadStringCompletedEventArgs e) { if (e.Error != null) { MessageBox.Show(e.Error.ToString()); return; } // (5).結果を解析して商品の一覧を作成する。 var xml = XElement.Parse(e.Result); var result = from item in xml.Descendants("Item") select new 商品 { 商品名 = item.Element("itemName").Value, 価格 = item.Element("itemPrice").Value, URL = item.Element("itemUrl").Value, }; 商品一覧.ItemsSource = result.ToList(); }
- 結果を解析して商品の一覧を作成する。
ここでは、Webサービスから取得したXMLを解析して、商品のリストを作成しています。楽天アイテム検索のページで出力XMLの形式を確認すると、Item要素以下に商品の一覧が格納されるため、LINQ to XMLを用いてItem要素以下のitemName、
itemPrice、
itemUrl
を取得し、商品クラスに格納しています。
その後、DataGridのItemsSource
に作成したリストを結びつけています。
この時に、LINQ to XMLの結果をそのままItemsSource
に代入すると、DataGridには何も表示されません。これはLINQ to XMLでは検索結果が必要になるまでリストの検索が実行されないためです。DataGridに結果を表示するためには、ItemsSource
の設定時に、ToList
メソッドで実体化させたリストを割り当てる必要があります。
その他のWebサービスにアクセスする場合
SilverlightのWebClient
では、基本認証がサポートされていません。また、前述のドメイン間ポリシーファイルで許可されているサイト以外にはアクセスできません。
筆者も最初はサンプルにTwitterクライアントを考えていたのですが、Twitterが基本認証を必要とすること、crossdomain.xmlで自サイト以外のアクセスを禁止していることから作成を断念しました。
このようなサービスを利用したい場合は、SilverlightをホストしているサイトにASP.NET WebServiceを作成し、サーバー側で必要なサービスに対しアクセスし、Silverlight側にはサーバー側でリクエストを行った結果をブリッジするような仕組みを考える必要があります。
まとめ
今回は、サービスリファレンスとWebClient
を使ったサーバーサービスとの連携について解説を行いました。Silverlightでは.NET Frameworkの機能が利用できるため、HTML+Javascriptでは実現できなかったクロスドメインや、複雑だったXMLの解析といった作業を簡単に行えることが理解いただけたのではないでしょうか。
Silverlightと言うと、動画やアニメーションなどのメディア方向ばかりに注目が集まっている感じがありますが、業務アプリケーションで利用する上では、.NET Frameworkの機能を利用してプログラムが作成できるというメリットこそが一番大きな特徴です。
ぜひもう一度この連載を確認してください。
Silverlightを業務アプリケーションで利用する際の助けになれればと願っています。