.NET TIPS

[ASP.NET AJAX]ダイナミック・コンテキスト機能で構造化データを受け渡しするには?[2.0、3.0、3.5、C#、VB]

山田 祥寛
2008/04/03

 「TIPS:[ASP.NET AJAX]ダイナミック・コンテキスト機能でポップアップ・コントロールの内容を動的に生成するには?」では、ダイナミック・コンテキスト機能を利用することで、ポップアップ・コントロールの内容をサービス・メソッドからの戻り値によって動的に決定する方法について紹介した。しかし、この方法には1つだけ難点がある。というのも、サービス・メソッドからの戻り値を整形済みのHTML形式(文字列)として返している点だ。この方法では、.aspxファイルでは一切のコーディングが不要であるという利点はあるものの、半面、サービス・メソッドを再利用しにくいという問題があるのだ(つまり、異なるページでスタイルだけが異なる同一のデータを利用したいという場合にも、異なるサービス・メソッドを用意しなければならない)。

 そこで本稿では、ダイナミック・コンテキスト機能でサービス・メソッドから構造化データを返す方法について紹介する。この方法を利用することで、HTML整形前の――純粋なデータだけをやりとりできるので、複数個所で同一のデータを利用したい場合にも、再利用が容易になる。

 ではさっそく、具体的なコード例を見ていくことにしよう。なお、本稿で紹介するサンプルは、「TIPS:[ASP.NET AJAX]ダイナミック・コンテキスト機能でポップアップ・コントロールの内容を動的に生成するには?」で紹介したサンプルをベースにしている。解説も差分について行っていくので、元のサンプルに関する詳細はこのTIPSを参照していただきたい。

1. XML Webサービス・クラスを修正する

 まずは、前掲のTIPSで作成したDynamicContext.asmxを構造化データ対応に修正してみよう。前掲のTIPSから修正しているのは、リスト内の太字の部分である。

<%@ WebService Language="C#" Class="DynamicContext" %>

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Text;
using System.Web;
using System.Web.Script.Services;
using System.Web.Script.Serialization;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService()]
public class DynamicContext  : System.Web.Services.WebService {

  [WebMethod]
  public string GetMenuContents(String contextKey) {

    List<MenuInfo> result = new List<MenuInfo>();

    ConnectionStringSettings setting =
      ConfigurationManager.ConnectionStrings["MyDB"];
    DbProviderFactory factory =
      DbProviderFactories.GetFactory(setting.ProviderName);

    using(DbConnection db = factory.CreateConnection()) {
      db.ConnectionString = setting.ConnectionString;
      DbCommand comm = factory.CreateCommand();
      comm.CommandText = "SELECT * FROM Menu WHERE category=@category";
      comm.Connection = db;
      DbParameter param = factory.CreateParameter();
      param.ParameterName = "@category";
      param.Value = contextKey;
      comm.Parameters.Add(param);
      db.Open();
      DbDataReader reader = comm.ExecuteReader();

      while (reader.Read()) {
        // 結果セットの内容を順にMenuInfoオブジェクトに格納し、
        // Listオブジェクトに追加
        MenuInfo menu = new MenuInfo();
        menu.Url = reader["url"].ToString();
        menu.Title = reader["title"].ToString();
        result.Add(menu);
      }
    }
    // Listオブジェクトの内容をシリアライズして戻り値として返す
    JavaScriptSerializer json = new JavaScriptSerializer();
    return json.Serialize(result);
  }
}

public class MenuInfo {
  public String Title;
  public String Url;
}
<%@ WebService Language="VB" Class="DynamicPopulate" %>

Imports System.Collections.Generic
Imports System.Data
Imports System.Data.Common
Imports System.Web
Imports System.Web.Script.Services
Imports System.Web.Script.Serialization
Imports System.Web.Services
Imports System.Web.Services.Protocols

<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ScriptService()> _
Public Class DynamicPopulate
  Inherits System.Web.Services.WebService
 
  <WebMethod()> _
  Public Function GetMenuContents(ByVal contextKey As String) As String

    Dim result As New List(Of MenuInfo)

    Dim setting As ConnectionStringSettings = _
      ConfigurationManager.ConnectionStrings("MyDB")
    Dim factory As DbProviderFactory = _
      DbProviderFactories.GetFactory(setting.ProviderName)

    Using db As DbConnection = factory.CreateConnection()
      db.ConnectionString = setting.ConnectionString
      Dim comm As DbCommand = factory.CreateCommand()
      comm.CommandText = "SELECT * FROM Menu WHERE category=@category"
      comm.Connection = db
      Dim param As DbParameter = factory.CreateParameter()
      param.ParameterName = "@category"
      param.Value = contextKey
      comm.Parameters.Add(param)
      db.Open()
      Dim reader As DbDataReader = comm.ExecuteReader()

      Do While reader.Read()
        ' 結果セットの内容を順にMenuInfoオブジェクトに格納し、
        ' Listオブジェクトに追加
        Dim menu As New MenuInfo()
        menu.Url = reader("url")
        menu.Title = reader("title")
        result.Add(menu)
      Loop
    End Using
    ' Listオブジェクトの内容をシリアライズして戻り値として返す
    Dim json As New JavaScriptSerializer()
    Return json.Serialize(result)
  End Function
End Class

Public Class MenuInfo
  Public Title As String
  Public Url As String
End Class
クライアント側からの要求を処理するためのXML Webサービス・クラス(DynamicContext.asmx)(上:C#、下:VB)

 ここで注目していただきたいのは、リスト内の部分だ。前掲のTIPSで紹介したように、ダイナミック・コンテキスト機能ではサービス・メソッドのシグニチャがあらかじめ決められており、戻り値として文字列型の値しか返すことができない。つまり、オブジェクトや配列のような構造化データを、そのままの形式でクライアント側に返すことはできないということだ。

 そこで登場するのが、JavaScriptSerializerクラス(System.Web.Script.Serialization名前空間)なのである。JavaScriptSerializerクラスは、ASP.NET AJAXで提供されるクラスの1つで、.NET Frameworkのオブジェクトや配列をJSONJavaScript Object Notation)形式に変換するための機能を提供するものだ。

 JSONとは、JavaScriptにおけるオブジェクト表記をほとんどそのままデータ・フォーマットとして採用した形式で、JavaScript上での操作が容易であるのが特徴である。AJAX通信でも、昨今ではデータ交換形式として、XMLよりもJSON形式を利用するのが一般的で、ASP.NET AJAXでもデフォルトではJSON形式が利用されている(詳細は、「TIPS:[ASP.NET AJAX]Webサービス・ブリッジ機能により構造化データを受け渡しするには?(応用編)」を参照)。

 ここでは、JavaScriptSerializerクラスのSerializeメソッドを利用して、Listオブジェクトの内容をJSON形式にシリアライズしているわけだ。JSON形式は、以下のようなテキスト形式であるので、ダイナミック・コンテキスト機能の標準的なサービス・メソッドで受け渡しすることが可能である。

[
  {
    "Title" : "Insider .NET",
    "Url"   : "http://www.atmarkit.co.jp/fdotnet/"
  },
  {
    "Title" : "WebDeli - ASP.NETツール集",
    "Url"   : "http://www.web-deli.com/"
  },
  {
    "Title" : "サーバサイド技術の学び舎 - WINGS",
    "Url"   : "http://www.wings.msn.to/"
  }
]
JSON形式のテキスト・データの例

2. クライアント・ページにJavaScriptのコードを追加する

 次に、受け取ったJSONデータを、クライアント・ページ(ここではDynamicContext.aspx)でHTML形式に整形してみよう。具体的には、以下のようなコードを追加する。

<script type="text/javascript">

function pageLoad() {

  // Behaviorオブジェクトを取得
  var beh = $find('dde');

  // populatedイベント・ハンドラを追加
  beh.add_populated(
    function(sender, args) {

      // 戻り値を解析し、オブジェクト配列として取得
      var result = eval($get('lblMenu').innerHTML);

      // 配列の内容を基に、アンカータグのリストを生成
      var builder = new Sys.StringBuilder();
      for(elem in result){
        builder.append(String.format("<a href='{0}' style='color:Navy;font-size:Small;text-decoration:none;'>{1}</a><br />", result[elem].Url, result[elem].Title));
      }
      // 生成されたHTMLを、ポップアップ・メニューに反映
      $get('lblMenu').innerHTML = builder.toString();
    } 
  );
}
</script>
サービス・メソッドから取得したJSONデータをHTML形式に整形し、ポップアップ・メニューに反映させるコード(DynamicContext.aspx)

 $find関数は、Microsoft AJAX Libraryで提供されるショートカット関数の一種で、ページで利用されているBehaviorオブジェクトを取得するためのものだ*1。ここでは、DropDownコントロールのBehaviorオブジェクトで公開されているpopulatedイベントに対して、イベント・ハンドラを登録している。populatedイベントは、ダイナミック・コンテキスト機能に対応したコントロールのBehaviorオブジェクトが共通して公開するイベントの1つで、非同期通信が完了したタイミングで発生する*2

*1 Behaviorオブジェクトとは、Extenderコントロールのクライアント側の挙動を定義するためのJavaScriptオブジェクトのこと。詳細は、「MS AJAX LibでAJAX対応コントロールを開発しよう(前編)」を参照。
*2 ちなみに、非同期通信の開始時に発生するpopulatingイベントもある。

 ここでは、populatedイベント・ハンドラで、サービス・メソッドから取得したMenuItemオブジェクト配列を基に表示するHTMLを整形しているというわけだ。

 サービス・メソッドからの戻り値(JSONデータ)は、DynamicContextIDプロパティで指定されたラベルlblMenuにセットされているはずなので、ここではこれを取得し、eval関数でJavaScriptオブジェクトとして変換している(繰り返しであるが、JSONデータはそれ自体がJavaScriptオブジェクトの表記であるので、eval関数で解析するだけで、そのまま利用できる)。JavaScriptオブジェクトの配列ができてしまえば、あとはごく当たり前の配列操作で中身にアクセスすることが可能である。

[参考]

  ここでは、Microsoft AJAX Libraryに含まれるSys.StringBuilderクラスとString拡張メソッドを使って、最終的な出力イメージを組み立てているが、複雑なレイアウトになってきた場合、このようなアプローチはコーディングを煩雑にする原因ともなる。

 そのような場合には、JavaScript対応のテンプレート・エンジンを利用することを検討してもよいだろう。テンプレート・エンジンとは、名前のとおり、テンプレート(ページ・レイアウトを定義したひな型)を解釈し、必要なデータを埋め込んだうえで出力するためのライブラリのこと。テンプレート・エンジンを利用することで、ロジック部分とレイアウト部分とを明確に分離できるので、コードが見やすく、メンテナンスも行いやすいというメリットがある。

 JavaScriptで利用可能なテンプレート・エンジンにはさまざまなものがあるが、以下には代表的なものをいくつか挙げておくことにしよう。

ライブラリ名 概要
JKL.Hina
(http://www.kawa.net/works/js/jkl/hina.html)
コンパクトな仕様と軽快な動作が特徴のライブラリ。日本語資料も充実している
AjaxPages
(http://ajax-pages.sourceforge.net/)
JSPやレガシーASPのようなHTML埋め込み型の構文を利用できるエンジン
Jamritas
(http://jamritas.sourceforge.net/)
ほかのライブラリと異なり、Ajax開発全般をサポートするライブラリ。テンプレート関係のほか、DOM周りのライブラリを提供
TrimPath JST
(http://trimpath.com/project/wiki/JavaScriptTemplates)
Smarty/Velocityなどのテンプレート・エンジンに酷似した構文を提供するライブラリ
JavaScriptで利用可能なテンプレート・エンジン

 以上の手順が完了したら、実際にコードを実行してみよう。「TIPS:[ASP.NET AJAX]ダイナミック・コンテキスト機能でポップアップ・コントロールの内容を動的に生成するには?」のサンプルと同様の結果が得られれば成功だ。

 サービス・メソッド、クライアント・ページのいずれでHTML形式に整形するかについては、一概にどちらがよりよいということはできない。開発生産性や再利用性から、その時々の状況でよりよいアプローチを選択してほしい。End of Article

利用可能バージョン:.NET Framework 2.0
利用可能バージョン:.NET Framework 3.0
利用可能バージョン:.NET Framework 3.5
カテゴリ:Webフォーム 処理対象:ASP.NET AJAX
使用ライブラリ:PopupControlコントロール
使用ライブラリ:ModalPopupコントロール
使用ライブラリ:HoverMenuコントロール
使用ライブラリ:DropDownコントロール
使用ライブラリ:JavaScriptSerializerクラス(System.Web.Script.Serialization名前空間)
関連TIPS:[ASP.NET AJAX]ダイナミック・コンテキスト機能でポップアップ・コントロールの内容を動的に生成するには?
関連TIPS:[ASP.NET AJAX]Webサービス・ブリッジ機能により構造化データを受け渡しするには?(応用編)

この記事と関連性の高い別の.NET TIPS
[ASP.NET AJAX]ダイナミック・コンテキスト機能でポップアップ・コントロールの内容を動的に生成するには?
[ASP.NET AJAX]Webサービス・ブリッジ機能により構造化データを受け渡しするには?(応用編)
[ASP.NET AJAX]Webサービス・ブリッジ機能により構造化データを受け渡しするには?(基本編)
JSON形式のデータの内容を確認するには?(JSON Viewer活用)
[ASP.NET AJAX]クライアントサイド・スクリプトからXML Webサービスを非同期呼び出しするには?(サーバサイド編)
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間