ASP.NET AJAX Web サービスについて理解する

作成者: Scott Cate

Web サービスは、.NET Framework を構成する不可分の要素として、分散システム間でデータを交換するためのクロスプラットフォーム ソリューションを実現するしくみです。 Web サービスは、通常、異なるオペレーティング システム間、オブジェクト モデル間、プログラミング言語間でのデータ送受信を実現する手段として使用されますが、動的に ASP.NET AJAX ページにデータを挿入する目的や、ページからバックエンド システムにデータを送信する目的にも使用できます。 これらは、いずれもポストバック操作に頼ることなく実現可能です。

ASP.NET AJAX を使用して Web サービスを呼び出す

Dan Wahlin

Web サービスは、.NET Framework を構成する不可分の要素として、分散システム間でデータを交換するためのクロスプラットフォーム ソリューションを実現するしくみです。 Web サービスは、通常、異なるオペレーティング システム間、オブジェクト モデル間、プログラミング言語間でのデータ送受信を実現する手段として使用されますが、動的に ASP.NET AJAX ページにデータを挿入する目的や、ページからバックエンド システムにデータを送信する目的にも使用できます。 これらは、いずれもポストバック操作に頼ることなく実現可能です。

ASP.NET AJAX UpdatePanel コントロールは、任意の ASP.NET ページを AJAX 対応にするシンプルな手段ではありますが、状況によっては、サーバー上のデータへの動的なアクセスを UpdatePanel 以外で実現することが必要になります。 この記事では、そうした場合の実現手段として、ASP.NET AJAX ページ内に Web サービスを作成して使用する方法を説明します。

この記事では、コア ASP.NET AJAX 拡張機能で提供されている機能と、ASP.NET AJAX Toolkit の AutoCompleteExtender と呼ばれる Web サービス対応コントロールを主に取り上げます。 説明するトピックは、AJAX 対応 Web サービスの定義、クライアント プロキシの作成、JavaScript による Web サービス呼び出しなどです。 また、ASP.NET ページ メソッドに対する Web サービス呼び出しを直接行う方法についても説明します。

Web サービスの構成

Visual Studio 2008 で新しい Web サイト プロジェクトを作成すると、web.config ファイルに、以前のバージョンの Visual Studio を使用してきた開発者には馴染みがない機能が多数追加されています。 それらの変更点は、ASP.NET AJAX コントロールに "asp" プレフィックスをマップしてページ内での使用を可能にするためのものや、必要な HttpHandlers と HttpModules を定義するものです。 リスト 1 に、web.config の <httpHandlers> 要素に加えられた変更のうち、Web サービス呼び出しに影響するものを示します。 .asmx 呼び出しの処理に使用される既定の HttpHandler は削除され、System.Web.Extensions.dll アセンブリにある ScriptHandlerFactory クラスに置き換えられています。 System.Web.Extensions.dll には、ASP.NET AJAX で使用されるすべてのコア機能が含まれています。

リスト 1。 ASP.NET AJAX Web サービス ハンドラーの構成

<httpHandlers>
     <remove verb="*" path="*.asmx"/>
     <add verb="*" path="*.asmx" validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory,
          System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
          PublicKeyToken=31bf3856ad364e35"/>
</httpHandlers>

これは、HttpHandler の代替となり、JavaScript Web サービス プロキシを使用して ASP.NET AJAX ページから .NET Web サービスへの JavaScript Object Notation (JSON) 呼び出しを可能にするために開発されたしくみです。 通常、Web サービスでは標準の Simple Object Access Protocol (SOAP) 呼び出しが使用されますが、ASP.NET AJAX は、Web サービスに対して JSON メッセージを送信します。 これには、要求メッセージと応答メッセージが全体的に小さくなるメリットがあります。 また、ASP.NET AJAX JavaScript ライブラリは JSON オブジェクトを扱いやすいように最適化されているため、クライアント側でのデータ処理をより効率的に行えます。 リスト 2 とリスト 3 に、Web サービスの要求メッセージと応答メッセージを JSON 形式にシリアル化したものの例を示します。 リスト 2 の要求メッセージでは、country パラメーターの値 "Belgium" を渡しています。リスト 3 の応答メッセージでは、Customer オブジェクトの配列とそれに関連するプロパティ群を渡しています。

リスト 2. JSON 形式にシリアル化された Web サービス要求メッセージ

{"country":"Belgium"}

> [!注] 操作名は Web サービスに送信する URL の一部として定義されます。また、要求メッセージは常に JSON 経由で送信されるとは限りません。 Web サービスは、ScriptMethod 属性の UseHttpGet パラメーターを true に設定して使用することができます。これはパラメーターの渡し方をクエリ文字列パラメーター経由にする使用方法です。

リスト 3. JSON 形式にシリアル化された Web サービス応答メッセージ

[{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Maison
     Dewey","CustomerID":"MAISD","ContactName":"Catherine
     Dewey"},{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Suprêmes
     délices","CustomerID":"SUPRD","ContactName":"Pascale
     Cartrain"}]

次のセクションでは、単純型と複合型の両方で JSON 要求メッセージの処理と応答を行う機能を持った Web サービスの作成方法を説明します。

AJAX 対応 Web サービスを作成する

ASP.NET AJAX フレームワークには、Web サービスを呼び出す方法が数種類用意されています。 AutoCompleteExtender コントロール (ASP.NET AJAX Toolkit で使用可能) または JavaScript のどちらも使用できます。 ただし、クライアント スクリプト コードからの呼び出しを可能にするには、サービスを呼び出す前に、そのサービスを AJAX 対応にする必要があります。

AJAX 対応サービスの作成は、ASP.NET Web サービスの使用経験があるかどうかにかかわらず、単純明快な作業です。 .NET Framework では、2002 年の最初のリリース時点から ASP.NET Web サービスの作成がサポートされています。ASP.NET AJAX 拡張機能は、.NET Framework の既定の機能セットを基礎として、その上に追加の AJAX 機能を提供するものです。 Visual Studio .NET 2008 Beta 2 には .asmx Web サービス ファイルの作成をサポートする機能が組み込まれており、関連するコードビサイド クラスは、System.Web.Services.WebService クラスから自動的に派生します。 クラスにメソッドを追加する際、Web サービス コンシューマーからのメソッド呼び出しを可能にするには、WebMethod 属性を適用する必要があります。

リスト 4 に、GetCustomersByCountry() というメソッドに WebMethod 属性を適用した例を示します。

リスト 4. Web サービス内で WebMethod 属性を使用する

[WebMethod]
public Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}

GetCustomersByCountry() メソッドは、country パラメーターを受け取り、Customer オブジェクトの配列を返します。 メソッドに渡された country の値はビジネス レイヤーのクラスに転送されます。そのクラスが、データ レイヤーのクラスを呼び出してデータベースからデータを取得し、Customer オブジェクトのプロパティにデータを設定して、この配列を返します。

ScriptService 属性を使用する

WebMethod 属性を追加すると、標準の SOAP メッセージを送信するクライアントからの GetCustomersByCountry() メソッド呼び出しが可能になりますが、これだけでは、ASP.NET AJAX アプリケーションから JSON 呼び出しを受け付けることはできません。 JSON 呼び出しができるようにするには、Web サービス クラスに ASP.NET AJAX 拡張機能の ScriptService 属性を適用する必要があります。 これは、Web サービスから JSON 形式を使用した応答メッセージを送信することと、クライアント側スクリプトから JSON メッセージを送信してサービスを呼び出すことを可能にする属性です。

リスト 5 に、CustomersService という Web サービス クラスに ScriptService 属性を適用した例を示します。

リスト 5. ScriptService 属性を使用して Web サービスを AJAX 対応にする

[System.Web.Script.Services.ScriptService]
[WebService(Namespace = "http://xmlforasp.net")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class CustomersService : System.Web.Services.WebService
{
     [WebMethod]
     public Customer[] GetCustomersByCountry(string country)
     {
          return Biz.BAL.GetCustomersByCountry(country);
     }
}

ScriptService 属性は、AJAX スクリプト コードからの呼び出しを受け付けることを示すマーカーとして機能します。 JSON のシリアル化タスクや逆シリアル化タスクを水面下で実際に行う働きはありません。 さまざまな JSON 処理を担当するのは、ScriptHandlerFactory (web.config で構成) とその他の関連クラスです。

ScriptMethod 属性を使用する

ScriptService 属性は、.NET Web サービスを ASP.NET AJAX ページで使用可能にするために Web サービスに適用することが必要な唯一の ASP.NET AJAX 属性です。 ただし、ScriptMethod という別の属性をサービス内の Web メソッドに直接適用することもできます。 ScriptMethod には、UseHttpGetResponseFormatXmlSerializeString の 3 つのプロパティが定義されています。 Web メソッドで受け付ける要求の種類を GET に変更する必要がある場合や、Web メソッドから生の XML データを XmlDocument または XmlElement オブジェクト形式で返す必要がある場合、または、サービスから返すデータを JSON 形式ではなく XML 形式で常にシリアル化する必要がある場合に、これらのプロパティの値を変更することが役立ちます。

UseHttpGet プロパティは、Web メソッドで POST 要求ではなく GET 要求を受け付ける必要があるときに使用します。 その場合、要求は URL を使用して送信され、Web メソッドの入力パラメーターは QueryString パラメーターに変換した形で渡されます。 UseHttpGet プロパティの既定値は false であり、true に設定することは、操作の安全性が確認されており、Web サービスに機密データが送信されない場合以外は推奨されません。 リスト 6 に、ScriptMethod 属性に UseHttpGet プロパティの指定を付ける場合の使用例を示します。

リスト 6. ScriptMethod 属性を使用する (UseHttpGet プロパティ指定あり)

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public string HttpGetEcho(string input)
{
     return input;
}

リスト 6 の HttpGetEcho Web メソッドが呼び出される際は、たとえば以下のようなヘッダーが送信されます。

GET /CustomerViewer/DemoService.asmx/HttpGetEcho?input=%22Input Value%22 HTTP/1.1

ScriptMethod 属性は、Web メソッドで HTTP GET 要求を受け付けるようにする場合のほか、サービスから返す応答の形式を JSON ではなく XML にする場合にも使用できます。 たとえば、リモート サイトから RSS フィードを取得し、XmlDocument オブジェクトまたは XmlElement オブジェクトとして返すような Web サービスがあるとします。 クライアントは、それを受け取って XML データとして処理することができます。

リスト 7 に、Web メソッドからの応答が XML データであることを ResponseFormat プロパティで指定した例を示します。

リスト 7. ScriptMethod 属性を使用する (ResponseFormat プロパティ指定あり)

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public XmlElement GetRssFeed(string url)
{
     XmlDocument doc = new XmlDocument();
     doc.Load(url);
     return doc.DocumentElement;
}

さらに、ResponseFormat プロパティは XmlSerializeString プロパティとあわせて指定することもできます。 XmlSerializeString プロパティの既定値は false です。これは、ResponseFormat プロパティの設定が ResponseFormat.Xml である場合に、Web メソッドから返される戻り値が、文字列型以外はすべて XML 形式にシリアル化されることを意味します。 XmlSerializeStringtrue に設定すると、Web メソッドから返される戻り値は、文字列型も含め、すべて XML 形式にシリアル化されます。 ResponseFormat プロパティの値が ResponseFormat.Json である場合、XmlSerializeString プロパティは無視されます。

リスト 8 に、文字列型に XML 形式でのシリアル化を適用することを XmlSerializeString プロパティで指定した例を示します。

リスト 8. ScriptMethod 属性を使用する (XmlSerializeString プロパティ指定あり)

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml,XmlSerializeString=true)]
public string GetXmlString(string input)
{
     return input;
}

リスト 8 の GetXmlString Web メソッドを呼び出すと、結果として以下のような値が返されます。

<?xml version="1.0"?>
     <string>Test</string>

既定の JSON 形式では、要求メッセージと応答メッセージの全体的なサイズを最小限に抑え、ブラウザーの違いに影響されない手軽な使い勝手を ASP.NET AJAX クライアントに提供できます。しかし、ある種のクライアント アプリケーション (例: Internet Explorer 5 以降) は、Web メソッドから XML 形式のデータが返されることを要求する場合があります。ResponseFormat プロパティと XmlSerializeString プロパティはそうした状況で役立ちます。

複合型の操作

前に示したリスト 5 は、Web サービスから Customer という名前の複合型を返す例でした。 この Customer クラスの内部には、FirstName、LastName など、単純型のプロパティが数個定義されています。 AJAX 対応の Web メソッドで、複合型を入力パラメーターまたは戻り値の型として使用する場合、それらはクライアント側に送信される前に JSON 形式へと自動的にシリアル化されます。 ただし、既定では、入れ子になった複合型 (別の型内で内部的に定義されている型) をスタンドアロンのオブジェクトとしてクライアントに提供することはできません。

Web サービスで使用している入れ子になった複合型をクライアント ページでも使用できるようにすることが必要な場合は、ASP.NET AJAX の GenerateScriptType 属性を Web サービスに追加します。 例えば、リスト 9 に示す CustomerDetails クラスには、入れ子になった複合型である Address プロパティと Gender プロパティが含まれています。

リスト 9. 入れ子になった複合型を 2 つ含んでいる CustomerDetails クラス

public class CustomerDetails : Customer
{
     public CustomerDetails()
     {
     }
     Address _Address;
     Gender _Gender = Gender.Unknown;
     public Address Address
     {
          get { return _Address; }
          set { _Address = value; }
     }
     public Gender Gender
     {
          get { return _Gender; }
          set { _Gender = value; }
     }
}

リスト 9 の CustomerDetails クラス内に定義されている Address オブジェクトと Gender オブジェクトは、ネストされた型 (Address はクラス、Gender は列挙型) であるため、そのままでは、クライアント側の JavaScript の中で使用できません。 Web サービス内で使用している入れ子になった型をクライアント側でも使用できるようにすることが必要な場合は、前述の GenerateScriptType 属性を使用できます (リスト 10 を参照)。 サービスから返される入れ子になった複合型が複数ある場合は、この属性を複数回指定できます。 また、Web サービス クラス全体に適用することも、特定の Web メソッドの上に適用することもできます。

リスト 10. 入れ子になった型をクライアントで使用可能にするために、GenerateScriptService 属性でそれらの型を定義する

[System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.GenerateScriptType(typeof(Address))]
[System.Web.Script.Services.GenerateScriptType(typeof(Gender))]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class NestedComplexTypeService : System.Web.Services.WebService
{
     //Web Methods
}

この Web サービスに GenerateScriptType 属性を適用することで、Address 型と Gender 型は、自動的にクライアント側 ASP.NET AJAX JavaScript コード内で使用できるようになります。 リスト 11 に、Web サービスに GenerateScriptType 属性を追加すると自動的に生成され、クライアントに送信される JavaScript の例を示します。 入れ子になった複合型の使用方法については、この記事の中で後述します。

リスト 11. 入れ子になった複合型を ASP.NET AJAX ページで使用できるようにする

if (typeof(Model.Address) === 'undefined')
{
     Model.Address=gtc("Model.Address");
     Model.Address.registerClass('Model.Address');
}
Model.Gender = function() { throw Error.invalidOperation(); }
Model.Gender.prototype = {Unknown: 0,Male: 1,Female: 2}
Model.Gender.registerEnum('Model.Gender', true);

Web サービスを作成する方法、ASP.NET AJAX ページからのアクセスを可能にする方法は、ここまで説明したとおりです。次は、Web サービスのデータの取得と送信ができるようにするための JavaScript プロキシを作成し、使用する方法を見てみましょう。

JavaScript プロキシを作成する

多くの場合、標準的な Web サービス (.NET または別のプラットフォーム) を呼び出す際には、SOAP 要求メッセージと応答メッセージの送信に関する煩雑さを意識させないためのプロキシ オブジェクトが作成されます。 ASP.NET AJAX Web サービス呼び出しにおいては、JavaScript プロキシを作成して使用することで、JSON メッセージのシリアル化と逆シリアル化に煩わされずサービスを簡単に呼び出せるようになります。 JavaScript プロキシは、ASP.NET AJAX ScriptManager コントロールを使用して自動的に生成できます。

Web サービスへの呼び出しが可能な JavaScript プロキシを作成するには、ScriptManager の Services プロパティを使用します。 このプロパティにより、ASP.NET AJAX ページからの非同期的な呼び出しでデータの送受信ができる、ポストバック操作が不要なサービスを 1 つまたは複数定義できます。 サービスを定義するには、ASP.NET AJAX ServiceReference コントロールを使用し、コントロールの Path プロパティに Web サービスの URL を割り当てます。 リスト 12 に示す例では、CustomersService.asmx という名前のサービスを参照しています。

<asp:ScriptManager ID="ScriptManager1" runat="server">
     <Services>
          <asp:ServiceReference Path="~/CustomersService.asmx" />
     </Services>
</asp:ScriptManager>

リスト 12. ASP.NET AJAX ページから使用される Web サービスを定義する

ScriptManager コントロールで CustomersService.asmx への参照を追加すると、JavaScript プロキシが動的に生成され、ページから参照されるようになります。 プロキシは <script> タグを使用して埋め込まれ、CustomersService.asmx ファイルへの呼び出しの末尾に /js を追加することで動的に読み込まれます。 JavaScript プロキシをページに埋め込む方法の例 (web.config でデバッグが無効になっている場合) を以下に示します。

<script src="CustomersService.asmx/js" type="text/javascript"></script>

> [!注] 生成された実際の JavaScript プロキシ コードを確認するには、目的の .NET Web サービスの URL を Internet Explorer のアドレス ボックスに入力し、末尾に /js を追加します。

web.config でデバッグが有効になっている場合は、以下の例のように、JavaScript プロキシのデバッグ バージョンがページに埋め込まれます。

<script src="CustomersService.asmx/jsdebug" type="text/javascript"></script>

ScriptManager によって作成された JavaScript プロキシは、<script> タグの src 属性で参照する方法のほか、ページに直接埋め込んで使用することもできます。 その場合には ServiceReference コントロールの InlineScript プロパティを true に設定します (既定値は false)。 この方法は、1 つのプロキシを複数のページで共用しないときや、サーバーに対するネットワーク呼び出しの発生回数を減らしたいときに便利です。 InlineScript を true に設定すると、プロキシ スクリプトがブラウザーのキャッシュに格納されなくなります。ASP.NET AJAX アプリケーションの複数のページから使用されるプロキシでは、既定値の false にしておくことをおすすめします。 InlineScript プロパティの使用例を以下に示します。

<asp:ServiceReference InlineScript="true" Path="~/CustomersService.asmx"/>

JavaScript プロキシを使用する

ScriptManager コントロールを使用して ASP.NET AJAX ページから Web サービスを参照すると、Web サービスの呼び出しと返されるデータの処理をコールバック関数で行えるようになります。 Web サービスの呼び出しは、そのサービスの名前空間 (存在する場合)、クラス名、および Web メソッド名を参照することで行われます。 Web サービスに渡すパラメーターの定義には、返されるデータを処理するコールバック関数の指定を含めることができます。

リスト 13 に、JavaScript プロキシを使用して GetCustomersByCountry() という Web メソッドを呼び出す例を示します。 GetCustomersByCountry() 関数は、ページ上のボタンがエンド ユーザーによってクリックされると呼び出されます。

リスト 13. JavaScript プロキシを使用して Web サービスを呼び出す

function GetCustomerByCountry()
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, OnWSRequestComplete);
}
function OnWSRequestComplete(results)
{
     if (results != null)
     {
          CreateCustomersTable(results);
          GetMap(results);
     }
}

この呼び出しでは、使用するサービスによって定義されている InterfaceTraining 名前空間、CustomersService クラス、GetCustomersByCountry Web メソッドを参照しています。 呼び出し時に、テキスト ボックスから取得した country 値と、非同期の Web サービス呼び出しが戻ってきたとき呼び出される OnWSRequestComplete というコールバック関数を渡しています。 OnWSRequestComplete では、サービスから返された Customer オブジェクトの配列を処理し、ページに表示するテーブルに変換します。 図 1 に、呼び出しによって生成される出力の例を示します。

Binding data obtained by making an asynchronous AJAX call to a Web Service.

図 1: Web サービスへの非同期 AJAX 呼び出しによって取得されたデータのバインド (クリックするとフルサイズの画像が表示されます)

また、Web メソッドを呼び出した後でプロキシに応答を待機させない動作が望ましい場合は、Web サービスに対する一方向の呼び出しだけを行う JavaScript プロキシにすることもできます。 たとえば、ワークフローなどのプロセスを開始する目的で Web サービスを呼び出す場合、サービスからの戻り値を待つ必要はないことがあります。 サービスに対して一方向の呼び出しを行う必要がある場合、リスト 13 に示すコールバック関数は単純に省略できます。 コールバック関数が定義されていなければ、プロキシ オブジェクトは Web サービスからデータが戻ってくるのを待ちません。

エラーの処理

Web サービスの非同期コールバックでは、さまざまな種類のエラーが発生する可能性があります (ネットワークのダウン、Web サービスの利用不可、または例外が返された場合など)。 このため、ScriptManager によって生成される JavaScript プロキシ オブジェクトには、既に見たような成功時のコールバックだけでなく、エラーや問題発生時に対応するためのコールバックを複数定義できます。 エラー コールバック関数は、リスト 14 に示す例のように、Web メソッドに対する呼び出しの中で、標準コールバック関数の直後に定義することができます。

リスト 14. エラー コールバック関数を定義してエラーを表示させる

function GetCustomersByCountry() 
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, 
          OnWSRequestComplete, OnWSRequestFailed);
}
function OnWSRequestFailed(error)
{
     alert("Stack Trace: " + error.get_stackTrace() + "/r/n" +
          "Error: " + error.get_message() + "/r/n" +
          "Status Code: " + error.get_statusCode() + "/r/n" +
          "Exception Type: " + error.get_exceptionType() + "/r/n" +
          "Timed Out: " + error.get_timedOut());
}

Web サービス呼び出しでエラーが発生すると、OnWSRequestFailed() コールバック関数が呼び出され、エラーを表すオブジェクトがパラメーターとして渡されます。 このエラー オブジェクトには、エラーの原因は何か、呼び出しのタイムアウトは発生したかの判断に使用できる数個の関数が用意されています。リスト 14 には各種エラー関数の使用例が含まれています。また、図 2 はこれらの関数によって生成される出力の例です。

Output generated by calling ASP.NET AJAX error functions.

図 2: ASP.NET AJAX エラー関数を呼び出すことで生成される出力 (クリックするとフルサイズの画像が表示されます)

Web サービスから返された XML データを処理する

既に説明したとおり、Web メソッドに ScriptMethod 属性を ResponseFormat プロパティの指定付きで適用すると、戻り値の形式を生の XML データにすることができます。 ResponseFormat として ResponseFormat.Xml を設定すると、Web サービスから返されるデータは JSON ではなく XML 形式でシリアル化されます。 これは、クライアントに XML データを直接渡して JavaScript または XSLT で処理する必要がある場合に便利です。 XML データの解析とフィルター処理に関して、現時点で最良のクライアント側オブジェクト モデルを備えているアプリケーションは、MSXML のサポートを内蔵している Internet Explorer 5 以降です。

Web サービスから XML データを取得する方法は、他のデータ型を取得する場合と変わりません。 まず、JavaScript プロキシを呼び出して適切な関数呼び出しを行い、コールバック関数を定義します。 呼び出しが戻ったときには、コールバック関数でデータを処理できます。

リスト 15 に、GetRssFeed() という名前の、XmlElement オブジェクトを返す Web メソッドの呼び出し例を示します。 GetRssFeed() は、取得する RSS フィードの URL を表すパラメーターを 1 つ受け取ります。

リスト 15. Web サービスから返された XML データを処理する

function GetRss()
{
     InterfaceTraining.DemoService.GetRssFeed(
          "https://blogs.interfacett.com/dan-wahlins-blog/rss.xml",
          OnWSRequestComplete);
}
function OnWSRequestComplete(result)
{
     if (document.all) //Filter for IE DOM since other browsers are limited
     {
          var items = result.selectNodes("//item");
          for (var i=0;i<items.length;i++)
          {
               var title = items[i].selectSingleNode("title").text;
               var href = items[i].selectSingleNode("link").text;
               $get("divOutput").innerHTML +=
               "<a href='" + href + "'>" + title + "</a><br/>";
          }
     }
     else
     {
          $get("divOutput").innerHTML = "RSS only available in IE5+";
     }
}

この例では、RSS フィードの URL を渡し、返された XML データを OnWSRequestComplete() 関数で処理しています。 OnWSRequestComplete() では、まず、MSXML パーサーを利用できるかを確認するために、ブラウザーが Internet Explorer かどうかをチェックします。 この条件に合致する場合は、XPath ステートメントを使用して、RSS フィード内のすべての <item> タグを検索します。 次に、反復処理で個々の項目を取得して、関連する <title> タグと <link> タグを見つけ、各項目のデータを処理して表示します。 図 3 に、JavaScript プロキシ経由で GetRssFeed() Web メソッドへの ASP.NET AJAX 呼び出しを行った場合に生成される出力の例を示します。

複合型を処理する

Web サービスが受け付ける複合型や返す複合型は、JavaScript プロキシを介して自動的に公開されます。 ただし、既に説明したとおり、入れ子になった複合型については、サービスに GenerateScriptType 属性を適用しない限りクライアント側で直接アクセスすることはできません。 では、入れ子になった複合型をクライアント側で使用することが必要なのは、どのような場合でしょうか?

この問いに答えるために、例として、ASP.NET AJAX ページに顧客データを表示し、エンド ユーザーから顧客住所の更新操作を受け付ける場合を考えてみましょう。 この Web サービスは Address 型 (CustomerDetails クラス内で定義される複合型) をクライアントに送信する仕様だとします。その場合、更新プロセスを複数の別々の関数に分けると、コードの再利用性を高めることができます。

Output creating from calling a Web Service that returns RSS data.

図 3: RSS データを返す Web サービスを呼び出すことで生成される出力 (クリックするとフルサイズの画像が表示されます)

リスト 16 に示すクライアント側コード例では、Model 名前空間内に定義された Address オブジェクトを呼び出し、更新データをそのオブジェクトに入力して、CustomerDetails オブジェクトの Address プロパティに代入します。 次に、その CustomerDetails オブジェクトを Web サービスに渡して処理させます。

リスト 16. 入れ子になった複合型を使用する

function UpdateAddress()
{
     var cust = new Model.CustomerDetails();
     cust.CustomerID = $get("hidCustomerID").value;
     cust.Address = CreateAddress();
     InterfaceTraining.DemoService.UpdateAddress(cust,OnWSUpdateComplete);
}
function CreateAddress()
{
     var addr = new Model.Address();
     addr.Street = $get("txtStreet").value;
     addr.City = $get("txtCity").value;
     addr.State = $get("txtState").value;
     return addr;
}
function OnWSUpdateComplete(result)
{
     alert("Update " + ((result)?"succeeded":"failed")+ "!");
}

ページ メソッドを作成して使用する

Web サービスは、ASP.NET AJAX ページなどのさまざまなクライアントに対して再利用可能なサービスを提供する手段として優れています。 ただし、場合によっては、特定のページにだけデータを提供する必要があり、そのデータは決して他のページでは使用も共有もされないことがあります。 サービスを使用するページが 1 つしかないとき、目的のページにデータ アクセスを提供するために .asmx ファイルを作成するのは大掛かりすぎると考えられる場合があります。

そこで、ASP.NET AJAX には、スタンドアロンの .asmx ファイルを作成しなくても Web サービスに似た呼び出しを実現できる別のメカニズムが用意されています。 これには "ページ メソッド" という手法が使用されます。 ページ メソッドは、WebMethod 属性を適用し、ページ内またはコードビサイド ファイル内に直接埋め込まれる静的な (VB.NET で共有される) メソッドです。 WebMethod 属性を適用すると、PageMethods という特別な JavaScript オブジェクト (実行時に動的に作成される) を使用してそれらのメソッドを呼び出すことが可能になります。 PageMethods オブジェクトは、JSON のシリアル化および逆シリアル化プロセスを開発者に意識させないためのプロキシとして機能します。 PageMethods オブジェクトを使用するには、ScriptManager の EnablePageMethods プロパティを true に設定する必要があります。

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
</asp:ScriptManager>

リスト 17 に、ASP.NET のコードビサイド クラス内に 2 つのページ メソッドを定義した例を示します。 これらのメソッドは、Web サイトの App_Code フォルダーにあるビジネス レイヤーのクラスからデータを取得します。

リスト 17. ページ メソッドを定義する

[WebMethod]
public static Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}
[WebMethod]
public static Customer[] GetCustomersByID(string id)
{
     return Biz.BAL.GetCustomersByID(id);
}

ScriptManager は、ページ内に Web メソッドがあることを検出すると、前述の PageMethods オブジェクトへの動的参照を生成します。 Web メソッドへの呼び出しは、PageMethods クラスへの参照の後にメソッド名を記述し、渡す必要があるパラメーター データを指定することで行われます。 リスト 18 に、上で示した 2 つのページ メソッドに対する呼び出しの例を示します。

リスト 18. PageMethods JavaScript オブジェクトを使用してページ メソッドを呼び出す

function GetCustomerByCountry() 
{
     var country = $get("txtCountry").value;
     PageMethods.GetCustomersByCountry(country, OnWSRequestComplete);
}
function GetCustomerByID() 
{
     var custID = $get("txtCustomerID").value;
     PageMethods.GetCustomersByID(custID, OnWSRequestComplete);
}
function OnWSRequestComplete(results) 
{
     var searchResults = $get("searchResults");
     searchResults.control.set_data(results);
     if (results != null) GetMap(results[0].Country,results);
}

PageMethods オブジェクトの使用方法は、JavaScript プロキシ オブジェクトと非常によく似ています。 まず、ページ メソッドに渡す必要があるすべてのパラメーター データを指定し、その後、非同期呼び出しが戻ったときに呼び出すコールバック関数を定義します。 さらに、問題発生時のコールバックを指定することもできます (問題発生時の対応方法については、リスト 14 の例を参照してください)。

AutoCompleteExtender と ASP.NET AJAX Toolkit

ASP.NET AJAX Toolkit (https://www.devexpress.com/Products/AJAX-Control-Toolkit から入手可能) には、Web サービスへのアクセスに使用できる数種類のコントロールが用意されています。 その中の 1 つである AutoCompleteExtender という便利なコントロールを使用すると、Web サービスを呼び出してページにデータを表示する処理を、JavaScript コードをまったく記述せずに実現できます。

AutoCompleteExtender は、テキスト ボックスの既存の機能を拡張し、ユーザーの求めているデータがより簡単に見つかるようにすることができるコントロールです。 テキスト ボックスに文字が入力されるのに従って Web サービスへのクエリを実行し、その結果をテキスト ボックスの下に動的に表示できます。 図 4 は、サポート アプリケーションに AutoCompleteExtender コントロールを使用して顧客 ID が表示されるようにした例です。 テキスト ボックスでユーザーの文字入力が行われるにつれて、入力内容に応じた異なる項目がテキスト ボックスの下に表示されます。 ユーザーは、表示された候補の中から目的の顧客 ID を選択できます。

ASP.NET AJAX ページ内で AutoCompleteExtender を使用するには、Web サイトの bin フォルダに AjaxControlToolkit.dll アセンブリを追加する必要があります。 追加した後は、このツールキット アセンブリを web.config から参照し、アセンブリに含まれているコントロールをアプリケーションのすべてのページで使用できるようにします。 これを行うには、web.config の <controls> タグ内に以下のタグを追加します。

<add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" tagPrefix="ajaxToolkit"/>

コントロールを使用する必要がある箇所が特定の 1 ページのみである場合は、web.config を更新するのではなく、当該ページの先頭に以下の Reference ディレクティブを追加してコントロールを参照することもできます。

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
     TagPrefix="ajaxToolkit" %>

Using the AutoCompleteExtender control.

図 4: AutoCompleteExtender コントロールを使用する (クリックするとフルサイズの画像が表示されます)

ASP.NET AJAX Toolkit を使用できるように Web サイトを構成した後は、ページに通常の ASP.NET サーバー コントロールを追加するのとよく似た方法で、AutoCompleteExtender コントロールを追加できます。 リスト 19 に、このコントロールを使用して Web サービスを呼び出す例を示します。

リスト 19. ASP.NET AJAX Toolkit の AutoCompleteExtender コントロールを使用する

<ajaxToolkit:AutoCompleteExtender ID="extTxtCustomerID" runat="server"
     MinimumPrefixLength="1" ServiceMethod="GetCustomerIDs"
     ServicePath="~/CustomersService.asmx"
     TargetControlID="txtCustomerID" />

AutoCompleteExtender には数種類のプロパティが用意されており、その中には、サーバー コントロールの標準的な ID プロパティや runat プロパティが含まれます。 加えて、Web サービスへのクエリを実行してデータを取得する前にエンド ユーザーからの入力を何文字要求するかを定義できます。 リスト 19 の例では、テキスト ボックスへの入力が 1 文字あるたびにサービスを呼び出すよう MinimumPrefixLength プロパティを設定しています。 ユーザーからの入力 1 文字ごとに、テキスト ボックス内の文字列に一致する値を検索するための Web サービス呼び出しが発生するため、この値の設定には注意が必要です。 ServicePath プロパティと ServiceMethod プロパティは、それぞれ、呼び出す Web サービスとターゲット Web メソッドの定義です。 最後に、TargetControlID プロパティでは、AutoCompleteExtender コントロールをフックする対象のテキスト ボックスを識別しています。

呼び出される Web サービスには、既に説明したとおり ScriptService 属性の適用が必要です。また、ターゲットの Web メソッドは prefixText と count という 2 つのパラメーターを受け付ける必要があります。 prefixText パラメーターはエンド ユーザーによって入力された文字列を表し、count パラメーターは返す項目の数を表します (既定値は 10)。 リスト 19 で示した AutoCompleteExtender コントロールから呼び出される GetCustomerIDs Web メソッドの例を、リスト 20 に示します。 この Web メソッドでは、ビジネス レイヤーのメソッドを呼び出しています。ビジネス レイヤーからはデータ レイヤーのメソッドが呼び出され、データを絞り込んで一致した結果を返す処理はそこで行われます。 そのデータ レイヤー メソッドのコードをリスト 21 に示します。

リスト 20. AutoCompleteExtender コントロールから受け取ったデータのフィルター処理を実行する

[WebMethod]
public string[] GetCustomerIDs(string prefixText, int count) 
{
     return Biz.BAL.GetCustomerIDs(prefixText, count);
}

リスト 21. エンド ユーザーからの入力に基づいて結果をフィルター処理する

public static string[] GetCustomerIDs(string prefixText, int count)
{
     //Customer IDs cached in _CustomerIDs field to improve performance
     if (_CustomerIDs == null)
     {
          List<string> ids = new List<string>();
          //SQL text used for simplicity...recommend using sprocs
          string sql = "SELECT CustomerID FROM Customers";
          DbConnection conn = GetDBConnection();
          conn.Open();
          DbCommand cmd = conn.CreateCommand();
          cmd.CommandText = sql;
          DbDataReader reader = cmd.ExecuteReader();
          while (reader.Read())
          {
               ids.Add(reader["CustomerID"].ToString());
          }
          reader.Close();
          conn.Close();
          _CustomerIDs = ids.ToArray();
     }
     int index = Array.BinarySearch(_CustomerIDs, prefixText, new CaseInsensitiveComparer());
     //~ is bitwise complement (reverse each bit)
     if (index < 0) index = ~index;
     int matchingCount;
     for (matchingCount = 0; matchingCount < count && index + matchingCount < _CustomerIDs.Length; matchingCount++)
     {
          if (!_CustomerIDs[index + matchingCount].StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase))
          {
               break;
          }
     }
     String[] returnValue = new string[matchingCount];
     if (matchingCount > 0)
     {
          Array.Copy(_CustomerIDs, index, returnValue, 0, matchingCount);
     }
     return returnValue;
}

まとめ

ASP.NET AJAX には、大量のカスタム JavaScript コード記述を不要にして要求メッセージと応答メッセージの処理をこなし、Web サービスの呼び出しをサポートする優れた機能があります。 この記事では、.NET Web サービスを AJAX に対応させて JSON メッセージの処理を可能にする方法と、ScriptManager コントロールを使用して JavaScript プロキシを定義する方法を説明しました。 また、JavaScript プロキシを使用して Web サービスを呼び出す方法、単純型と複合型の扱い方、問題発生時の対応方法についても説明しました。 最後に、ページ メソッドを使用してシンプルに Web サービス呼び出しの作成と実行のプロセスを実現する方法と、AutoCompleteExtender コントロールを使用してエンド ユーザーに入力支援を提供する方法を説明しました。 ASP.NET AJAX の UpdatePanel はシンプルに使用でき、多くの AJAX プログラマーに最適なコントロールであることは間違いありません。とはいえ、JavaScript プロキシを使用して Web サービスを呼び出す方法を知っておくと、さまざまなアプリケーションに役立ちます。

経歴

Dan Wahlin 氏 (Microsoft MVP - ASP.NET および XML Web サービス) は、Interface Technical Training (http://www.interfacett.com) の .NET 開発インストラクター兼アーキテクチャ コンサルタントです。 XML for ASP.NET Developers Web サイト (www.XMLforASP.NET) 創設者、INETA Speaker's Bureau 会員。講演者として多数のカンファレンスの登壇実績があります。 書籍『Professional Windows DNA』(Wrox)、『ASP.NET: Tips, Tutorials and Code』(Sams)、『ASP.NET 1.1 Insider Solutions』、『Professional ASP.NET 2.0 AJAX』(Wrox)、『ASP.NET 2.0 MVP Hacks』の共著者、『XML for ASP.NET Developers』(Sams) の著者。 コーディング、記事執筆、書籍執筆以外の時間には、妻や子供たちと一緒に、音楽の作曲とレコーディング、ゴルフやバスケットボールを楽しんでいます。

Scott Cate 氏は、1997 年以来 Microsoft Web テクノロジの活用に携わり続けています。myKB.com (www.myKB.com) の社長として、ナレッジ ベース ソフトウェア ソリューションを中心とした ASP.NET ベースのアプリケーション開発を専門に手掛けます。 問い合わせは、メール: scott.cate@myKB.com、またはブログ: ScottCate.com まで。