« 2006年8月 | トップページ | 2006年10月 »

2006年9月26日 (火)

.NETのリフレクション

.NET FrameworkのクラスライブラリにXmlSerializerというクラスがあります。
インスタンスのプロパティ値をXML形式でシリアライズしてくれるクラスで、簡単な使用例は以下のような感じです。

UserAccount.cs(この例ではAPS.NETで使用していますので、App_Code内に作成します)
using System;

public class UserAccount
{
    private string userName;

    public string UserName
    {
        get { return userName; }
        set { userName = value; }
    }

    private string mailAddress;

    public string MailAddress
    {
        get { return mailAddress; }
        set { mailAddress = value; }
    }
}

Serialize.aspx
<%@ Page Language="C#" %>

<script runat="server">
    void Page_Load()
    {
        UserAccount account = new UserAccount();
        account.UserName = "ユーザー名";
        account.MailAddress = "account@hoge.com";

        System.Xml.Serialization.XmlSerializer serializer =
            new System.Xml.Serialization.XmlSerializer(typeof(UserAccount));

        serializer.Serialize(Response.OutputStream, account);
        
        Response.End();
    }

</script>

このSerialize.aspxにアクセスすると、次のようなXMLが返ってきます。
<?xml version="1.0"?>
<UserAccount xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <UserName>ユーザー名</UserName>
  <MailAddress>account@hoge.com</MailAddress>
</UserAccount>
クラスのデータがXMLで出力されていますね。
便利なクラスです。

JSONでも出力したい、という要望が出たらどうしましょうか。
残念ながら、JSONでシリアライズするクラスはありませんので、JSONで出力するJsonSerializerというクラスを作ってみましょう。

データを保存するためには、まずオブジェクトが持つプロパティを列挙し、その値を取得出来なければなりません。
型情報からプロパティやメソッドの情報を取得したりするには、リフレクションを使います。

実際にやってみましょう。
JsonSerializer.cs
using System;
using System.IO;
using System.Reflection;

public class JsonSerializer
{
    private Type type;

    public JsonSerializer(Type type)
    {
        this.type = type;
    }

    public void Serialize(Stream stream, object instance)
    {
        string data = "";
        
        PropertyInfo[] properties = type.GetProperties();
        for (int i = 0; i < properties.Length; i++)
        {
            PropertyInfo property = properties[i];
            data += String.Format("'{0}' : '{1}'", 
                property.Name, property.GetValue(instance, null));
                
            if (i != properties.Length - 1)
                data += ",";
        }

        string json = type.Name + " = {" + data + "};";

        using (StreamWriter writer = new StreamWriter(stream))
        {
            writer.Write(json);
            writer.Close();
        }
    }
}
プロパティがさらにプロパティを持っていたりする場合が多いですので、実際に使用できるレベルにするにはもっと複雑になりますが、とりあえずはこんな感じです。

リフレクションの中心となるのはTypeというクラスで、型情報を持っています。
ObjectのGetType()メソッド、または typeof でインスタンスを取得します。

Typeクラスには、その型のメンバーを取得するGetPropertiesメソッドやGetMethodsメソッドなどがあり、今回はGetPropertiesメソッドでプロパティ を取得しています。
PropertyInfo[] properties = type.GetProperties();

このメソッドを呼び出すと、PropertyInfoというプロパティの情報を持つクラスの配列が戻ってきます。
PropertyInfoクラスは、プロパティの名前を取得したり、実際のインスタンスを渡して値を取得したりすることが出来ます。
PropertyInfo property = properties[i];
data += String.Format("'{0}' : '{1}'", property.Name, property.GetValue(instance, null));

それでは、このクラスを試してみましょう。
Serialize.aspxを以下のように書き換えます。
<%@ Page Language="C#" %>

<script runat="server">
    void Page_Load()
    {
        UserAccount account = new UserAccount();
        account.UserName = "ユーザー名";
        account.MailAddress = "account@hoge.com";

        JsonSerializer serializer = new JsonSerializer(typeof(UserAccount));

        serializer.Serialize(Response.OutputStream, account);
        
        Response.End();
    }

</script>

Serialize.aspxにアクセスすると、以下のようなデータが返ってきます。
(わかりやすいように改行を入れていますが、実際には改行はされません)
UserAccount = {
    'UserName' : 'ユーザー名',
    'MailAddress' : 'account@hoge.com'
};
JSON形式で出力されました。
なお、XMLとの対比という意味で、UserAccountというクラス名の部分も出力しています。

| | コメント (0) | トラックバック (0)

2006年9月20日 (水)

XMLHttpRequestを使わずにデータを受信する

XMLHttpRequestを使ったAjaxでの通信は、セキュリティ上の制限により他ドメインのサーバーにはアクセスできません。
サーバー側のプログラムを自分で記述できる場合は、一旦中継してデータを取得し好きな形式に変換するプログラムを書けばよいのですが、 例えばこのブログのようにJavaScriptしか記述できない場合はそうもいきません。

クライアントがXMLHttpRequestを使わずに他ドメインのサーバーからデータを取得できれば…ということになるわけですが、それを実現する 方法の1つとして、サーバーが結果をJavaScriptのコードで返すというものがあります。

例えば、
http://www.hoge.com/service.aspx?param=xxxxx
のようにリクエストすると、paramの内容に応じた結果をJSONデータを含むJavaScriptコードで返してくれるサービスがあった場合、 クライアント側では、
<script type="text/javascript" src="http://www.hoge.com/service.aspx?param=xxxxx"></script>
という風にしてJavaScriptコードを取り込めば、ドメインとは無関係にデータを利用できます。

scriptタグのsrc属性に指定するURLは他ドメインであっても問題ないという特性を利用したもので、 ちょうど、imgタグのsrcに指定する画像ファイルはどこのサーバーにあっても問題なく画像が表示されるのと同じですね。

このscriptタグをdocument.createElementで動的に生成すれば、XMLHttpRequestを使った場合と同様に動的にデータを取得して表示することが 可能になります。
ただし、その場合は、JavaScriptのコードが読み込み終わった時点で関数を呼び出すなどして読み込みが完了したことを通知しなければ、 クライアント側ではデータを処理できません。


わかり辛いと思いますので、少し具体的に例を書いてみます。

http://www.hoge.com/service.aspx?callback=handle&param=xxxxx
とリクエストすると、
handle({'result':{'item1':'value1'}});
という結果を返すサービスがあったとします。
URLパラメータのcallbackには通知を受ける関数名を指定します。

このサービスを利用する際、クライアントは以下のようなコードでデータを受信することが出来ます。

function getData()
{
    var script = document.createElement('script');
    script.src = 'http://www.hoge.com/service.aspx?callback=handle&param=xxxxx';

    document.body.appendChild(script);
}

function handle(data)
{
    alert(data.result.item1);
}
ボタンを押すなどのイベント時に getData() を呼び出すと、"value1" が表示されます。

こういった利用方法を想定してサーバーが返すデータはJSONP(JSON with Padding)と呼ばれていて、Yahoo! US、Amazon、Flickrなどは すでにこの形式でのサービスも提供しています。

でも、JavaScriptコードを返すということで、セキュリティ上好ましくないという意見もあります。

| | コメント (0) | トラックバック (0)

2006年9月14日 (木)

ClientCallback機能を使ったTreeViewの展開

ASP.NET 2.0から追加されたTreeViewコントロールですが、ClientCallbackを使ってポストバックなしでデータを動的にバインドする機能があります。

まず、ページにTreeViewを配置し、TreeViewノードエディタでノードを追加します。
追加したノードのプロパティで、下図のようにExpandedをFalseに、PopulateOnDemandをTrueに設定します。
また、TreeView自体のPopulateNodesFromClientプロパティもTrueにします。




次に、TreeViewを配置したページに TreeNodePopulate イベントハンドラを作成します。
ツリーを展開すると、このイベントハンドラが呼び出されます。




ここでは、子ノードを10個追加するコードを記述してみます。
protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        TreeNode node = new TreeNode("Node" + i);
        e.Node.ChildNodes.Add(node);
    }
}
これで終わりです。
JavaScriptを1行も書くことなく、ClientCallbackを使ってポストバックなしにノードを追加出来るようになりました。




HTMLソースを見ると、JavaScriptはWebResource.axdというファイルをインクルードするscriptタグが出力されていることがわかると思います。
これにより、JavaScriptをまったく書かずに実現できたわけですね。
興味のある方は、どんなスクリプトが出力されているか覗いてみたら面白いと思います。


なお、このファイルは実際に存在しているものではなく、WebResource.axd?d=xxxxxx&t=xxxxxxにリクエストするとAssemblyResourceLoaderクラスにより アセンブリに埋め込まれているリソース(スクリプトや画像ファイルなど)をレスポンスとして返す仕組みになっていて、ファイルの識別はd=xxxxxxの部分で 行われています。
ツリーの横に表示されている + - の画像も、同じようにリソースから読み込まれています。

リソースの埋め込み、読み出しについては、また後日サンプルを書いてみたいと思います。

| | コメント (0) | トラックバック (0)

2006年9月11日 (月)

ASP.NETのClientCallback

ASP.NETの開発でAjaxを使う機会も増えているんじゃないかと思います。
今日は、ASP.NET2.0から追加された機能である、ClientCallbackの使い方などを書いてみたいと思います。

ClientCallbackとは、文字通りクライアントからサーバーのメソッドがコールバックされる仕組みで、通信にはXMLHttpRequestが使われています。
通信部分のスクリプトはASP.NETが勝手に出力してくれますので、私達は簡単なスクリプトを少し書くだけでOKといった感じですね。

もちろん、Ajaxでゴリゴリやる場合はそうもいきませんが、例えば郵便番号が入力されたら住所を自動入力するといった補助的なものなら、割と簡単に行うことができます。


ClientCallback使うには、いくつかの決まり事があります。
まず1つ目の決まりとして、そのページまたはコントロールが ICallbackEventHandler インターフェイスを実装していなければなりません。
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Example : System.Web.UI.Page, ICallbackEventHandler
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    public string GetCallbackResult()
    {
        //ここに、クライアントに値を返すコードを書く
        return "Hello World";
    }

    public void RaiseCallbackEvent(string eventArgument)
    {
        //このメソッドが、クライアントから呼び出される
    }
}
このRaiseCallbackEventがクライアントからコールバックされ、GetCallbackResultメソッドにクライアントに値を返すコードを実装します。
しかし、これだけではまだコールバックイベントは発生しません。

2つ目の決まりとして、当然ではありますが、コールバックイベントを発生させるための関数とサーバーから値を受け取る 関数がそれぞれJavaScriptで定義されていなければなりません。
そこで、まず aspx に値を受け取る関数を記述します。
<script type="text/javascript">
function ReceiveData(data)
{
    alert(data);
}
</script>
名前は何でも構いませんが、ここではReceiveDataという名前の関数を作成しました。
引数は、サーバーから返される値になります。

次に、コールバックを発生させるJavaScriptの関数を作成する必要がありますが、これはサーバー側で作成して出力します。
とりあえず、Page_Loadメソッドに記述してみましょう。
protected void Page_Load(object sender, EventArgs e)
{
    string callBackRef =
        Page.ClientScript.GetCallbackEventReference(
            this, "arg", "ReceiveData", "context");

    string script = "function CallServerMethod(arg, context){" + callBackRef + ";}";
    
    Page.ClientScript.RegisterClientScriptBlock(
        this.GetType(), "CallbackScript", script, true);
}
まず、ClientScriptManagerのGetCallbackEventReferenceメソッドを使って、ASP.NETが自動生成するJavaScriptの関数を呼び出すコードを得ます。
GetCallbackEventReferenceメソッドの引数は、
第1引数:ICallbackEventHandlerを実装しているページまたはコントロール
第2引数:コールバックを実行するJavaScriptの関数の第1引数の名前
第3引数:結果を受け取るJavaScriptの関数名
第4引数:コールバックを実行するJavaScriptの関数の第2引数の名前
となっています。

恐らく第2引数と第4引数がわかり辛いと思います。
ココの説明はMSDNの記述も意味がよくわかりませんが、単に、その後に記述してある
string script = "function CallServerMethod(arg, context){" + callBackRef + ";}";
の呼び出し側JavaScript関数の引数である "arg"、"context"の変数名を渡せばOKです。
ちなみに、このJavaScript関数の第1引数はサーバーに渡される値、第2引数はサーバーのメソッドをコールバックする前に実行するJavaScriptの関数またはコードになります。

あとは、このスクリプトを RegisterClientScriptBlock メソッドでページに登録して完了です。
ただ、この RegisterClientScriptBlock メソッドはASP.NET 2.0から変更されています。
以前はPageクラスのRegisterClientScriptBlockメソッドを呼び出して登録していましたが、2.0からはこのメソッドは使わずにClientScriptManagerの RegisterClientScriptBlockメソッドを使って登録します。

最後に、以下のようボタンを追加して登録したJavaScriptの関数を実際に呼び出してみましょう。
<button onclick="CallServerMethod();">Call</button>
Hello Worldのalertが表示されると思います。

| | コメント (0) | トラックバック (0)

ミハエル・シューマッハが引退発表

技術系の話題しか書かないつもりでしたが、私にとって大きなニュースですのでちょっと脱線します。


昨日のイタリアGP決勝後の記者会見で、ミハエル・シューマッハが引退を発表しました。
大方の噂どおりですし、レース後のパルクフェルメや表彰台での振る舞いは「やっぱり引退かなぁ」と思わせるものではありましたが、本人の口から発表されると、やはりなんとも言えない気分になりました。
私は彼のファンではないです(むしろアンチです)が、来年からいないのかと思うと寂しいですね。

シューマッハのことを初めて知ったのは、F1デビュー前、全日本F3000に1戦だけスポット参戦したときでした。
たまたまテレビで観ていたのですが、2位に入賞したこともあってか、シューマッハの名前が強く印象に残っていたのを覚えています。

その後、1991年のベルギーGPでF1デビュー戦では、決して戦闘力が高いとは言えないジョーダンのマシンで予選7番グリッドを獲得し、次レースでのベネトンへの電撃移籍は物議を醸しました。
デビューしたその時から、すでに彼はF1界のスターの仲間入りをしていたように思います。


その後、シューマッハは数々の記録を塗り替えてきましたが、アラン・プロストのファンだった私は、プロストが保持していた51勝の最多勝記録を更新された時、非常にガッカリしました。
そんな最多勝記録も、昨日のイタリアGPで90勝まで更新し、私としてはここまで来たら100勝を達成して欲しいと思っていたんですけどねぇ。

あとは、残り3戦を無事に走りきって、8度目のワールドチャンピオンを獲得して締め括って欲しいですね。

| | コメント (0) | トラックバック (1)

2006年9月 7日 (木)

Flex2の日本語ドキュメントが公開

Flex2 日本語ドキュメントが公開されたようです。
開発も楽になりますね。

| | コメント (0) | トラックバック (0)

2006年9月 5日 (火)

Flex2のカスタムコントロールにイベントを作成する

独自のコントロールを作成したら、次は独自のイベントを発行する必要が生じると思いますので、今日は先日作成したカスタムコントロールに イベントを追加するサンプルを書いてみます。
サンプルのMXMLファイル(CustomEvent.zip)

まず、独自のイベントを定義してみます。
イベントオブジェクトは、flash.mx.events.Eventのサブクラスである必要があります。
今回は、イベントオブジェクトの定義にActionScriptファイル(asファイル)を使用します。
ExampleBox.mxmlと同じディレクトリに、ExampleBoxButtonClickEvent.asという名前のファイルを作成し、以下のコードを記述します。
package controls
{
    import flash.events.Event;
	
    public class ExampleBoxButtonClickEvent extends Event
    {
        public var message:String;
        
        public function ExampleBoxButtonClickEvent(eventType:String)
        {
            super(eventType, false, false);
        }
    }
}
このサンプルでは、単純にメッセージを持つ変数だけのシンプルなものにしました。

先頭の package controls は、このイベントクラスがcontrolsパッケージに属することを示しています。
クラス宣言時の extends Event により、このクラスはEventクラスのサブクラスとなります。
コンストラクタ内の super は、スーパークラス(この場合はEventクラス)のコンストラクタを呼び出しています。
ここまでは、Javaの経験がある方ならおなじみですね。

なお、Eventクラスのコンストラクタは、
第1引数 : イベントタイプを表す文字列
第2引数 : イベントバブリング(親要素へイベントが伝播する)を行うかどうか
第3引数 : イベントをキャンセルできるかどうか
を指定するようです。


次に、ExampleBoxがイベントを発行する部分を記述しましょう。
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox borderStyle="solid" 
    xmlns:mx="http://www.adobe.com/2006/mxml"
    initialize="initBox()"
    width="300" height="300">
    
    
    <mx:Metadata>
        [Event(name="exampleButtonClick", 
            type="controls.ExampleBoxButtonClickEvent")]
    </mx:Metadata>
    

    <mx:Script>
    <![CDATA[
    
    private var boxTitle:String = "";
    
    private function initBox() : void
    {
        this.titleLabel.text = this.boxTitle;
    }
    
    public function set title(value:String) : void
    {
        this.boxTitle = value;
        
        if(this.titleLabel != null)
            this.titleLabel.text = value;
    }
    
    public function get title() : String
    {
        return this.boxTitle;
    }
    
    
    private function buttonClick() : void
    {
        var event:ExampleBoxButtonClickEvent = 
            new ExampleBoxButtonClickEvent("exampleButtonClick");
            
        event.message = "ボタンがクリックされました";
            
        dispatchEvent(event);
    }
    
    
    ]]>
    </mx:Script>
    
    <mx:Label id="titleLabel" />
    <mx:Button label="click" click="buttonClick()" />
</mx:VBox>
太字の部分が、先日のExampleBox.mxml ファイルに追加したコードです。
今回は、ボタンを1つ追加して、そのボタンが押されたときにイベントを発生させます。

まず、このコントロールが持つイベントを、
<mx:Metadata>
    [Event(name="exampleButtonClick", type="controls.ExampleBoxButtonClickEvent")]
</mx:Metadata>
の部分で定義しています。
[Event ( name="EventName", type="EventClassName" ) ]
といった書式で、EventNameはイベント名となり、EventClassNameで指定したクラスはイベントハンドラに引数として渡されます。

実際にイベントを発行している部分は、ボタンのクリックイベントハンドラである
private function buttonClick() : void
{
    var event:ExampleBoxButtonClickEvent = 
        new ExampleBoxButtonClickEvent("exampleButtonClick");
    
    event.message = "ボタンがクリックされました";
    
    dispatchEvent(event);
}
の部分で、まずイベントハンドラに渡すExampleBoxButtonClickEventのインスタンスを生成します。
第1引数にはイベントタイプを渡しますが、この文字列はイベント名と同じものを指定する必要があるようです。
その後、必要なデータを格納した後に dispatchEvent(event) でイベントを発行します。
dispatchEventメソッドは flash.events.EventDispatcher クラスのメソッドで、今回のVBoxはこのクラスのサブクラスとなっています。


最後に、このイベントを処理する部分のコードを追加します。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:components="controls.*"
    width="100%" height="100%"
    horizontalAlign="left">
    
    <mx:Script>
    <![CDATA[
    import controls.*;
    
    private function parts1ButtonClickHandler
        (event:ExampleBoxButtonClickEvent) : void
    {
        mx.controls.Alert.show(event.message);
    }
    
    ]]>
    </mx:Script>
    
    
    <mx:HBox>
        <components:ExampleBox title="共通パーツ1" 
            exampleButtonClick="parts1ButtonClickHandler(event)"
            backgroundColor="#5555ff" />
        <components:ExampleBox title="共通パーツ2" backgroundColor="#ff5555" />
    </mx:HBox>
    
</mx:Application>
太字の部分が追加したコードです。
ExampleBoxタグに、追加したイベント名であるexampleButtonClick="..."としてイベントハンドラを指定出来ていますね。

あとは、ボタンなどと同様にイベントを処理します。
このサンプルでは、Alertでイベントオブジェクトの message 文字列を表示しています。

| | コメント (3) | トラックバック (0)

« 2006年8月 | トップページ | 2006年10月 »