ASP.NET の開発で重宝するのが System.Web.UI.DataBinder だが、使いづらい場合がある。
DataBinder で主に使われるのが Eval メソッドだが、このメソッドが何をしているのかというと、単にリフレクションを行っているだけだ。
C# 2.0
using System;
using System.Collections.Generic;
using System.Web.UI;
public class TestData
{
private int _id;
private string _name;
public int ID
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class Program
{
static void Main( string[] args )
{
List<TestData> list = new List<TestData>();
TestData d = new TestData();
d.ID = 1;
d.Name = "A";
list.Add( d );
Console.WriteLine( DataBinder.Eval( list[ 0 ], "ID" ) );
Console.WriteLine( DataBinder.Eval( list[ 0 ], "Name" ) );
}
}
Visual Basic 8.0
Imports System
Imports System.Collections.Generic;
Imports System.Web.UI
Public Class TestData
Private _id As Integer
Private _name As String
Public Property ID() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
Module Program
Public Sub Main()
Dim list As New List(Of TestData)
Dim d As New TestData()
d.ID = 1
d.Name = "A"
list.Add(d)
Console.WriteLine(DataBinder.Eval(list(0), "ID"))
Console.WriteLine(DataBinder.Eval(list(0), "Name"))
End Sub
End Module
実行結果
1
A
実際はこんな使い方をする事は無いと思うが、DataBinder の働きを検証するには十分だろう。
ASP.NET 1.x の開発では DataGrid で DataBinder.Eval を多用すると思う。DataSource にバインドしたデータの一項目が Container.DataItem としてアクセスできるからだ。
※Container.DataItem とは何か?巧く説明している記事が @IT にある。
[ASP.NET]Container.DataItemの正体は?
※ちなみに DataBinder.Eval に関しても @IT に。
[ASP.NET]DataBinder.Evalメソッドを使用するメリット/デメリットは?
さて、DataBinder.Eval が上手く機能しないシナリオが次だ。
C# 2.0
using System;
using System.Collections.Generic;
using System.Web.UI;
public class TestData
{
public int ID;
public string Name;
}
public class Program
{
static void Main( string[] args )
{
List<TestData> list = new List<TestData>();
TestData d = new TestData();
d.ID = 1;
d.Name = "A";
list.Add( d );
Console.WriteLine( DataBinder.Eval( list[ 0 ], "ID" ) );
Console.WriteLine( DataBinder.Eval( list[ 0 ], "Name" ) );
}
}
Visual Basic 8.0
Imports System
Imports System.Collections.Generic;
Imports System.Web.UI
Public Class TestData
Public ID As Integer
Public Name As String
End Class
Module Program
Public Sub Main()
Dim list As New List(Of TestData)
Dim d As New TestData()
d.ID = 1
d.Name = "A"
list.Add(d)
Console.WriteLine(DataBinder.Eval(list(0), "ID"))
Console.WriteLine(DataBinder.Eval(list(0), "Name"))
End Sub
End Module
実行結果
ハンドルされていない例外 System.Web.HttpException: DataBinding: 'TestData' には ID という名前のプロパティは含まれません。
最初のコードとの違いは、フィールドをプロパティでカプセル化しているかどうかだ。しかし、TestData のクライアントからすれば、フィールドであろうがプロパティであろうが使い方は同じである。
「実行結果の例外メッセージは的確に問題を指摘している。'ID という名前のプロパティがない'と言っているから、プロパティを定義してやればよいのだ」誰もがそう指摘するだろう。
確かにその通りで、フィールドを直接公開するのは一部の例外を除きご法度のはずである。そういう意味では DataBinder に不備などないように思う。しかし、Web サービスとの連携では DataBinder は使い物にならない。
ご存知の通り、Visual Studio には Web サービスへのプロキシクラスを自動生成する機能がある(Web 参照という機能だ)。この自動生成されたクラスで使用する DTO は全て Public フィールドを公開している。Public プロパティではない。
「Public プロパティではなく、Public フィールドを公開する」
これは単なる DTO なのだから問題はないと思う。しかし、これら DTO の配列などを DataGird 等にバインドし、DataBinder を使うと途端に破綻する。Public フィールドを DataBinder がリフレクションで取り出さないからだ。
「DataBinder を使わずにキャストすれば良いのでは?」
確かにその通りであり、その方法で十分に機能する。
例えば、
<%# DataBinder.Eval(Container.DataItem, "ID" %>
としている箇所を
<%# ((TestData)Container.DataItem).ID %>
とすれば良いのだ。暫くはこれで問題ないと思っていた。
しかし、DataBinder は甘くなかった。Sysetm.Web.UI.WebControls.ListControl から派生ししている全てのクラスは DataTextField プロパティを持っているが、コイツが内部で DataBinder.Eval を使っているのだ。DropDownList に Web サービスプロキシの DTO をデータバインドして、表示する項目を DataTextField に指定すると、DataBinder.Eval に Public フィールドを指定できない問題が顔を出す。
DataTextField という名前になっているのが、更に奇妙に感じる。ここで言う「フィールド」はクラスの「フィールド」ではなく、DataTable の「フィールド」だったのだ。様々なコントロールが DataSet と連携する事が前提になっている好例で、名前付けにも垣間見える。
ASP.NET 2.0 になって、DataBinder は変わったのか?と思ったがそんな事は全く無い。GridView は DataBinder を使っていないようだが、結局内部では DataBinder と似たような事をしていて、Public フィールドは指定できない。
リフレクションでフィールドを取得する事も出来るのにプロパティ限定にした理由は何だろうか。明らかに余計な制限だと思うのだが、どうだろうか?