MSDN マガジン > Home > 発行物 > 2008 > November >  データ ポイント: Silverlight 2 での Web サービス利用に関する疑問点
データ ポイント
Silverlight 2 での Web サービス利用に関する疑問点
John Papa

コードのダウンロード: MSDN Code Gallery (151 KB)
オンラインでのコードの参照
このコラムは、Silverlight 2 のプレリリース バージョンに基づいています。ここに記載されているすべての情報は、変更される場合があります。
Silverlight を使用している開発者たちは、数々の疑問で頭の中がぼんやりしているように思えますが、それは無理もありません。多種多様な Web サービスからリッチなインターネット アプリケーション (RIA) のデータを取得する処理は、ますます多くの場面で使用されるようになっています。Silverlight アプリケーションは、ASMX Web サービス、Windows Communication Foundation (WCF) Web サービス、Representational State Transfer (REST) サービス、および Plain Old XML (POX) サービスと通信できます。これらのサービスがサードパーティから提供されている場合も、Silverlight アプリケーションと同じサーバーでホストされているカスタム サービスである場合も、Silverlight では、データを要求し、そのデータを処理して、Silverlight クライアント アプリケーションとこのような Web サービスとの間でデータをやり取りできます。
これまでに筆者の元に寄せられた質問からは、開発者が Silverlight 2 アプリケーションからのサービスの呼び出し、WCF、ASMX、および REST の各サービスを使用する場合の違い、そしてこれらのサービスが公開するデータの処理方法について、詳しく知りたいと考えていることがわかります。今回のデータ ポイントでは、寄せられた質問の一部に回答すると共に、データ サービスの利用方法について説明します。
独自の API へのアクセスからクライアント固有のカスタム データの格納まで、あらゆる機能を提供するサードパーティの Web サービスが多数存在します。ここでは、Silverlight アプリケーションとサービスのクラウドとの間で行われるデータの処理とやり取りについて説明します。また、いくつかの XML 解析ライブラリを使用してデータを処理する方法も紹介します。たとえば、XML ドキュメントを開いたり、反復処理したりできるほか、LINQ to XML を使用して XML のクエリを行うこともできます。Flickr、Amazon、Twitter、Live Search などのサービスは、いずれもさまざまな Web サービスの手法を通じて通信できる API を公開しています。ここでは、これらの API の一部について説明し、REST ベースのサービスおよび SOAP ベースのサービスを使用してこれらの API と通信する方法を示します。このコラムのすべてのサンプルは、MSDN Magazine の Web サイトからダウンロードできます。C# と Visual Basic の両方のバージョンを用意しました。

Silverlight 2 では Web サービスはどのように処理されるのですか。
これは、最初に取り上げるのにふさわしい質問です。これほど多くの開発者が Web サービスとの通信に Silverlight 2 を使用している理由を理解する土台となるためです。Silverlight 1.x は Microsoft .NET Framework に基づくコードをサポートしておらず、.NET コントロールもありませんでした。Silverlight 2 には、これまでの制限を取り払う完全な機能が数多く搭載されています。Silverlight 2 を使用すると、コードを C# または Visual Basic で記述し、これまでに培った .NET CLR に関するあらゆる経験を活かすことができます。Silverlight に含まれるライブラリは .NET ライブラリ全体のサブセットですが、Silverlight 2 には多数の機能が用意されています。たとえば、WebClient クラスと HttpWebRequest クラスは Silverlight からアクセスでき、URI を呼び出すことで Web ベースのサービスとの通信に使用できます。データは、XmlReader オブジェクトまたは LINQ to XML を通じて処理できます。
Silverlight 2 には、サービス間でデータをやり取りするための多数の機能が追加されています。次に示すのは、Silverlight 2 の新機能です。
  • プロキシ クラスを通じて SOAP ベースの Web サービスにアクセスできます。
  • REST ベースの Web サービスにアクセスできます。
  • ADO.NET Data Services (リモート LINQ クエリが可能な REST ベースのサービス) にアクセスできます。
  • JavaScript Object Notation (JSON) と XML の両方を使用して、Web サービスから結果を取得できます。
  • WCF を通じて (サーバー プッシュを使用して) 二重通信がサポートされます。
  • clientaccesspolicy.xml ファイルまたは crossdomain.xml ファイルを通じてクロスドメイン アクセスがサポートされます。
  • HTTP とソケットを使用して、クロスドメイン ネットワークのサポートを利用できます。
  • Web サービスの呼び出しが非同期に開始されます。

データはどのような方法でサービスから Silverlight 2 に渡されるのですか。
図 1 に、Silverlight 2 でアクセスできる数種類のサービスを示します。Silverlight 2 とこれらのサービスの間では、データを XML、JSON、またはスカラ値の形でやり取りできます。SOAP ベースのサービスは、それ自身を記述することができます。したがって、これらのサービスが公開するデータも記述できます。Silverlight 2 クライアントは、サービスのプロキシを作成することで、SOAP ベース サービスのデータにアクセスできます。このプロキシは、公開されるクラスの記述を生成します。
図 1 Silverlight からサービスにアクセスする
SOAP ベースのサービスはそれ自身を記述するため、Silverlight 2 のクライアント アプリケーションは、公開されたエンティティを使用してデータをサービスとやり取りできます。ASMX Web サービスと WCF Web サービスでは、クライアント参照を使用して、エンティティをそのコントラクトの一部として含めることができます。クライアント参照は、公開されるクラスとサービス メソッド (サービス メソッドは、すべて Silverlight 2 クライアントの非同期呼び出しに変換されます) の定義が含まれるプロキシ クラスをクライアントで生成します。
POX ベースのサービスや REST ベースのサービスなど、それ自身を記述しないサービスでは、クライアント アプリケーションはそのサービス メソッドを呼び出し、データをスカラ形式で、あるいは XML または JSON の形で取得できます。これらの種類のサービスは、Web サービス記述言語 (WSDL) を公開しません。したがって、クライアント アプリケーションはサービスのプロキシ クラスを生成しません。サービスに対するクエリは、クラス (WebClient クラスや HttpWebRequest クラスなど) を通じて、URI を使用して行われます。

ASMX Web サービスではデータはどのような方法で処理されるのですか。
ASMX サービスと同様に、SOAP ベースの WCF Web サービスも、WSDL を使用してそれ自身を記述します。クライアントがサービスへの参照を追加する場合、クライアントはサービスのクライアント プロキシを生成します。これにより、クライアントはサービスとの間でデータをネイティブな形式で (つまりクラスとして) やり取りできます。たとえば、Dog エンティティを SOAP ベースの Web サービス (ASMX または WCF) から Silverlight 2 クライアントに返すことができます。その後、Silverlight 2 クライアントは Dog クラスのインスタンスを作成できます。メソッドは、パブリック サービス メソッドを WebMethod 属性で修飾し、ASMX Web サービス クラスを WebService 属性で修飾することで、ASMX Web サービスのクライアントに公開されます。図 2 に、これを C# で示します。
[WebService(Namespace = "http://www.microsoft.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class TestService : System.Web.Services.WebService {
  [WebMethod]
  public List<Dog> GetDogList() {
   return new List<Dog> {new Dog {Name = "Spot", Age = 10},      new Dog {Name = "Kadi", Age = 13}};
  }
}

[Serializable]
public class Dog {
  public string Name { get; set; }
  public int Age { get; set; }
}
この例は、Silverlight 2 クライアント アプリケーションの .NET を対象とするコードを示しています。このコードは、SOAP サービスのプロキシを生成します。イベント ハンドラが GetDogListCompleted イベントに割り当てられた後、サービスの非同期呼び出しが実行されます。Silverlight 2 からの Web サービスの呼び出しは、すべて非同期に行われます。サービスによって UI スレッドが停止し、アプリケーションがフリーズするのを防ぐためです。次に、結果をエンティティ型の配列として受け取るイベント ハンドラを示します。
TestServiceSoapClient proxy = new TestServiceSoapClient();
proxy.GetDogListCompleted +=   new EventHandler<GetDogListCompletedEventArgs>(proxy_  GetDogListCompleted);
proxy.GetDogListAsync();
次に、エンティティを受け取るための C# コードを示します。
void proxy_GetDogListCompleted(object sender,   
  GetDogListCompletedEventArgs e)
{
  Dog[] list = e.Result;
}

WCF Web サービスではデータはどのような方法で処理されるのですか。
ASMX Web サービスと同様に、WCF Web サービスも WSDL を使用してそれ自身を記述します。WCF Web サービスは、一連の属性を使用して渡すことのできるデータとサービスを公開します。したがって、クライアント アプリケーションは WCF Web サービスを参照し、プロキシ クラスを生成して、サービスや公開されたデータ コントラクトとやり取りできます。
WCF では、WCF Web サービスから返すことのできる任意のカスタム .NET 型としてデータ コントラクトを定義します。これらの型は、DataContract 属性で修飾されます。クラスのプロパティは、DataMember 属性で修飾される必要があります。図 3 に、適切な属性で修飾されたサンプル クラスを示します。
[DataContract]
public class Employee {
  int _employeeID;
  string _firstName;
  string _lastName;
  string _title;
  DateTime _hireDate;
  byte[] _photo;

  [DataMember]
  public int EmployeeID {
    get { return _employeeID; }
    set { _employeeID = value; }
  }
  [DataMember]
  public string FirstName {
    get { return _firstName; }
    set {_firstName = value; }
  }
  [DataMember]
  public string LastName {
    get { return _lastName; }
    set { _lastName = value; }
  }
}
DataContract 属性と DataMember 属性を使用して WCF Web サービスのデータ形式を定義した後で、サービスとそのサービス メソッドを設定する必要があります。サービスは ServiceContract 属性で修飾され、そのメソッドは OperationContract 属性で修飾されます。サービスは必要に応じてインターフェイスを実装できます。その場合は、インターフェイス (サービス クラスではありません) とそのインターフェイス メソッドを属性で修飾します。これらの属性で修飾された IEmployeeService とそのメンバを次に示します。
   [ServiceContract(Namespace = "")]
   public interface IEmployeeService
   {
    [OperationContract]
    List<Employee> FindEmployees();
   }
ここで、Silverlight 2 アプリケーションが処理できるのは basicHttpBinding を使用する SOAP ベースの WCF サービスだけであることに注意してください。
Silverlight 2 クライアント アプリケーションは、このサービスへのサービス参照を追加できます。このサービス参照がプロキシ クラスを作成します。その後、ASMX Web サービスの場合と同様に、サービスを非同期に呼び出すことができます。結果のキャプチャにはハンドラが役立ちます。プロキシと完了イベント ハンドラを実装するためのコードには、ASMX Web サービスで示したコードとまったく同じ形式を使用します。データが List<Employee> の形で返された後、サービス メソッドを通じて、エンティティの反復処理、データ バインド、変更、および送信を適宜行うことができます。

データは REST Web サービスからどのような方法で受信するのですか。
REST サービスは、パラメータの含まれるクエリ文字列を使用して、URI を通じて呼び出すことができます。このサービスは、WebClient クラスまたは HttpWebRequest を使用して、Silverlight 2 から呼び出せます。WebClient を使用して呼び出す方が簡単ですが、HttpWebRequest を使用すると、要求の実行方法をよりきめ細かく制御できます。また、WebClient は UI スレッドで戻りますが、HttWebRequest はバックグラウンド スレッドで戻ります。HttpWebRequest のコールバックでは、Dispatcher を使用して UI とやり取りする必要があります。次のコードは、Digg サービスから最新の記事を受信する System.Net.WebClient を示しています。
string baseUri = "http://services.digg.com/stories/topic";
string topic = txtTopic.Text;
string appKey = "http%3A%2F%2Fwww.microsoft.com";
int count = int.Parse(txtTopicCount.Text);
string url = String.Format("{0}/{1}?appkey={2}&count={3}", baseUri, topic, appKey, count);
WebClient svc = new WebClient();
svc.DownloadStringAsync(new Uri(url));
この場合、データは WebClient の呼び出しから未加工の XML として返されます。その後で、データの解析とクエリを実行できます。REST サービスにパラメータがある場合、それらのパラメータはクエリ文字列の一部として渡すことができます。

XML のクエリの実行方法を教えてください。
呼び出された REST ベースのサービスまたは POX サービスは、データを XML として返すことができます (REST サービスは、データを JSON として返すこともできます)。その後、さまざまな XML 解析ライブラリを使用して XML を解析できます。ただし、LINQ to XML を使用して XML のクエリを行うこともできます。LINQ to XML は、複雑な XML 階層の反復処理を行うことなくデータを XML 構造から抽出するリッチなクエリ インターフェイスを提供します。
先ほど紹介した例の REST サービスでは、最新の記事を表示する Digg サービスから未加工の XML を返します。この XML は XML ライブラリを使用して解析できます。また、図 4 に示すように、LINQ to XML を使用してクエリを行うこともできます。XDocument クラスの Parse メソッドは、前に示した REST サービスからの XML を処理できます。その後、LINQ to XML を使用して XML のクエリを実行できます。図 5 に示す XML のクエリを行うための LINQ to XML 構文を、図 4 に示します。
XDocument xml = XDocument.Parse(rawXml);
var storiesQuery = from story in xml.Descendants("story")
  select new DiggStory
    {
       Id = (int)story.Attribute("id"),
       Title = ((string)story.Element("title")).Trim(),
       Description = ((string)story.Element("description")).Trim(),
       ThumbNail =
         (story.Element("thumbnail") == null
              ? string.Empty
              : story.Element("thumbnail").Attribute("src").Value),
       Link = new Uri((string)story.Attribute("link")),
       DiggCount = (int)story.Attribute("diggs")
    };
<?xml version="1.0" encoding="utf-8" ?>
<stories timestamp="1222547017" min_date="1219955010" total="2541"
                                                 offset="0" count="10">
  <story link="http://news.msn.com.us/2/hi/technology/7540282.stm"    
       submit_date="1222545893" diggs="1" id="8725656" comments="0" 
       href="http://digg.com/microsoft/Microsoft_releases_SQL_Server_
       2005_recently" status="upcoming" media="news">
    <description>Microsoft has released SQL Server 2005 recently
       as an upgrade to the popular SQL Server database.</description>
    <title>Microsoft releases SQL Server 2005</title>
    <user name="D3a1i0" icon="http://digg.com/img/udl.png" 
      registered="1104422772" profileviews="5616" />
    <topic name="Microsoft" short_name="microsoft" />
    <container name="Technology" short_name="technology" />
    <thumbnail originalwidth="226" originalheight="170" contentType=
      "image/jpeg" src="http://digg.com/microsoft/Microsoft_releases_SQL_
      Server_2005_recently/t.jpg" width="80" height="80" />
  </story>
  <story link="http://navigatetrends.blogspot.com/2008/09/microsofts-
      virtual-receptionist.html" submit_date="1222545326" diggs="1"
      id="8725560" comments="0" href="http://digg.com/microsoft/
      Microsoft_s_virtual_receptionist" status="upcoming" media="videos">
    <description>Microsoft's virtual receptionist</description>
    <title>Microsoft's virtual receptionist</title>
    <user name="billarunk" icon="http://digg.com/img/udl.png" 
      registered="1216563445" profileviews="341" fullname="bill.arunk"
    />
    <topic name="Microsoft" short_name="microsoft" />
    <container name="Technology" short_name="technology" />
    <thumbnail originalwidth="400" originalheight="254" contentType="image/
      jpeg" src="http://digg.com/microsoft/Microsoft_s_virtual_
      receptionist/t.jpg" width="80" height="80" />
  </story>
...
...
</stories>
クエリは stories/story 階層で開始され、すべての story 要素に対して実行されます。この XML に対して指定されている名前空間はありませんが、結果が名前空間を参照する場合にすべての XML パスにプレフィックスを付ける方法を示すため、名前空間変数 (ns) を残しました。story 要素は、XDocument インスタンスの Descendants メソッドを使用して処理します。story 要素のプロパティは、story 変数とその Element メソッドを使用して取得できます。たとえば、次のコード行では、story 要素から title 要素の値を取得し、それを Title という名前のプロパティに設定します。
Title = ((string)story.Element("title")).Trim()
LINQ to XML クエリでは、投影を通じて、またはクエリ構造に対してデータを返すことができます。図 4 に示すコードでは、DiggStory クラスを作成して、XML データから取得した各 status 要素の結果を格納しています。開発者が定義した DiggStory クラスでは、コードをわかりやすくするため、すべてのプロパティが文字列として定義されています。LINQ to XML には、その他の要素 (順序付けやフィルタ処理の条件など) を必要に応じて追加することもできます。
REST サービスまたは POX サービスから取得した XML データのクエリと解析に LINQ to XML を使用するとき、開発者は他の形式の LINQ に関する既存の知識を活かすことができます。LINQ の構文は、どれも非常によく似ているためです。データの解析に LINQ to XML を使用した場合の最大のメリットは、入れ子になった foreach ループで要素を反復処理することなく、データのクエリを簡単に実行できるという点でしょう。

サードパーティの Web サービスにアクセスするとき、ドメイン間ポリシーに注意する必要はありますか。
Silverlight 2 アプリケーションが Web サービスの呼び出しを行うと、Web サービスのサーバーでドメイン間ポリシー ファイルがチェックされ、この呼び出しが無効になることがあります。このチェックは、Silverlight アプリケーションが Web サービスのドメインとは別のドメインにホストされているときに行われます。たとえば、図 6 は、Web サーバー johnpapa.net にホストされている Silverlight 2 アプリケーションが、同じく johnpapa.net にホストされている Web サービスを呼び出せることを示しています。これは、両者が同じドメインに配置されているためです。ただし、johnpapa.net にホストされている Silverlight 2 クライアントが microsoft.com にホストされている Web サービスを呼び出す場合、Silverlight 2 はドメイン間ポリシー ファイルが microsoft.com に存在し、呼び出しを許可していることを確認します。
\\msdnmagtst\MTPS\MSDN\issues\en\08\11\Papa - DataPoints.1108\figures\fig06.gif
図 6 ドメイン間のチェック
ドメイン間ポリシーの詳細については、2008 年 9 月号のコラム「Silverlight 2 と WCF を使用したサービス駆動型アプリケーション」を参照してください。このコラムでは、ファイル形式とポリシーの機能について説明しています。
Silverlight 2 では、対象の Web サーバーがそのドメイン間ポリシー ファイルを通じて呼び出しを許可している場合にのみ、ドメイン間呼び出しを行うことができます。WCF Web サービスや REST ベースのサービスなど、呼び出す Web サービスの種類は問いません。例として、前のコードをもう一度見てみましょう。
string baseUri = "http://services.digg.com/stories/topic";
string topic = txtTopic.Text;
string appKey = "http%3A%2F%2Fwww.microsoft.com";
int count = int.Parse(txtTopicCount.Text);
string url = String.Format("{0}/{1}?appkey={2}&count={3}", baseUri,  
  topic, appKey, count);
WebClient svc = new WebClient();
svc.DownloadStringAsync(new Uri(url));
このコードは、Digg Web サービスを呼び出して最新の記事にアクセスします。Digg では、Flash 形式を使用してドメイン間ポリシー ファイルを http://services.digg.com/crossdomain.xml に保持しています。
この Digg ドメイン間ファイルの内容を次に示します (この記事の執筆時点での内容です)。
<cross-domain-policy>
  <allow-access-from domain="*"/>
</cross-domain-policy>
この Digg ドメイン間ファイルではあらゆるドメインからの呼び出しが許可されていますが、制限を加えている Twitter ドメイン間ファイルと同様に、呼び出しを制限できます。次のコードの Twitter ドメイン間ファイルに注目してください。
<?xml version="1.0" encoding="UTF-8" ?> 
<cross-domain-policy  
  xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:
  noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/ 
  PolicyFile.xsd">
  <allow-access-from domain="*.twitter.com" /> 
  <site-control permitted-cross-domain-policies="master-only" /> 
  <allow-http-request-headers-from domain="*.twitter.com" /> 
</cross-domain-policy>
Twitter のドメイン間ファイルでは、呼び出しは *.twitter.com ドメインからの呼び出しに制限されています。Silverlight 2 アプリケーションが twitter.com ドメインにホストされていない場合、Silverlight クライアントは Twitter の REST ベース サービスに対する Web サービス要求を直接行うことはできません。このとき、プロキシ サービスが役立ちます。たとえば、Twitter.com Web サービスを呼び出す WCF Web サービスを作成し、それを自分のドメインにホストできます。その WCF サービスからの呼び出しは、Silverlight アプリケーションと Twitter 間で情報を中継します。サーバーで実行されている WCF サービスには Silverlight クライアントとは違ってドメイン間ポリシー ファイルの制限が適用されないため、呼び出しを行うことができます。この方法とは別に、Popfly や Yahoo! のクラウド サービスなどのサービスを通じて呼び出しを中継する方法もあります。

ご質問やご意見は、John (mmdata@microsoft.com) まで英語でお送りください。

John Papa (johnpapa.net) は、ASPSOFT (aspsoft.com) の上級コンサルタントです。野球を熱狂的に愛し、夏の夜を家族と共にヤンキースの応援に費やします。C# の MVP であり、INETA の講演者でもある John は、何冊かの書籍を発表しており、現在も最新作の『Data Access with Silverlight 2』を執筆中です。また、主にカンファレンス (DevConnections、VSLive など) での講演で活躍しています。

Page view tracker