面向对象开发中的数据控件绑定方案
在dotNET中,DataGrid, Repeater等控件的数据源只需要是一个从IEnumerable接口派上的对象就可以进行绑定了,所以可以将DataTable, DataView等作为数据源。
在面向对象开发的中,一般我们会返回一个对象集合而不是一个DataTable, 而dotNet中的集合对象(如Array, ArrayList等)都实现了IEnumerable接口,故而也可以直接将对象集合赋值给数据控件的数据源。
在本文中重点要讨论是如何定制绑定的属性,如标题等。
当不需要在同一页面修改数据时(我称之为原地编辑),我一般选择Repeater来显示数据,如果要原地编辑数据,则使用DataList,因为它们给予了页面和代码最大程度上的分离,这对于美工来说是非常重要的。下面先给出示例代码(部分)
//*** Product.aspx ***
<table>
<asp:Repeater id="rptProduct" runat="server">
<headertemplate>
<tr>
<td><asp:Label id="ProductId" runat="server"/></td>
<td><asp:Label id="Name" runat="server"/></td>
<td><asp:Label id="UnitPrice" runat="server"/></td>
</tr>
</headertemplate>
<itemtemplate>
<tr>
<td><asp:Label id="ProductId" runat="server"/></td>
<td><asp:Label id="Name" runat="server"/></td>
<td><asp:Label id="UnitPrice" runat="server"/></td>
</tr>
</itemtemplate>
</asp:Repeater>
</table>
这里定义了标题模板和项目模板, Label控件的值在后台代码中进行设置, 注意,控件的名称应取对象的属性名。
//*** Product.cs ***
public class Product {
public Product() { }
[Caption("产品Id")]
public int ProductId
{
get { return productId; }
set { productId = value; }
}
private int productId;
[Caption("名称")]
public string Name
{
get { return name; }
set { name = value; }
}
private string name;
[Caption("单价")]
public decimal UnitPrice
{
get { return unitPrice; }
set { unitPrice = value; }
}
private decimal unitPrice;
} //class Product 产品业务对象
//*** CaptionAttribute.cs ****
public class CaptionAttribute : Attribute {
public CaptionAttribute( string text ) {
this.text = text;
}
public string Text
{
get { return text; }
}
private string text;
} //class CaptionAttribute 标题特性对象
在这里简单的传入一个标题文本,如果需要多语言支持,应该传入一个资源Id,然后读取对应的资源字符串。
//*** Product.aspx.cs ***
void Page_Load( object sender, EventArgs e ) {
if ( !IsPostBack ) {
rptProduct.DataSource = GetProducts(); // GetProducts返回Product对象的集合,这里省略.
rptProduct.DataBind();
}
}
void rptProduct_ItemDataBound( object sender, RepeaterItemEventArgs e ) {
if ( ListItemType.Header == e.Item.ItemType )
ValueHelper.SetCaption( e.Item, typeof(Product) );
else if ( ListItemType.Item == e.Item.ItemType ||
ListItemType.AlternatingItem == e.Item.ItemType )
{
Product p = (Product)e.Item.DataItem;
ValueHelper.SetData( e.Item, p );
}
}
//*** ValueHelper.cs ***
public class ValueHelper {
public static void SetCaption( Control container, Type type ) {
foreach ( Control c in container.Controls ) {
if ( c is Label ) {
Label label = (Label)c;
if ( !IsEmpty(label.Text) ) {
PropertyInfo pi = type.GetProperty( label.ID );
label.Text = label.Id // 默认的标题
if ( pi != null ) {
object[] attributes = pi.GetCustomAttributes( typeof(CaptionAttribute), true );
if ( attributes.Length > 0 )
label.Text = ((CaptionAttribute)attributes[0]).Text;
}
} //end if ( IsEmpty )
}
else if ( c.HasControls() ) {
SetCaption( c, type );
}
} //end foreach ( container.Controls )
}
// 遍历所有的Label控件,然后根据其ID值在类类型中查找属性,再在属性上取得定制的Caption特性。
public static void SetData( Control container, Object obj ) {
foreach ( Control c in container.Controls ) {
if ( c is Label || c is TextBox )
SetControlData( c, obj, c.ID );
else if ( c.HasControls() )
SetData( c, obj );
}
}
// 遍历所有的Label和TextBox控件,然后设置其值。
private static void SetControlData( Control c, Object obj, string propertyName ) {
if ( c == null || obj == null ) return;
PropertyInfo pi = obj.GetType().GetProperty( propertyName );
if ( pi != null ) {
object value = pi.GetValue( obj, null );
if ( value != null ) {
if ( c is Label )
((Label)c).Text = value.ToString();
else if ( c is TextBox )
((TextBox)c).Text = value.ToString();
}
}
}
// 通过反射取得对象属性的值,然后赋与控件。这里还可以根据值的类型就值进行格式化,这个留给大家自由发挥了。
} //class ValueHelper
通过定制特性我们不难扩展出像FormattedAttribute, VisiableAttribute, WidthAttribute等特性来。
另一种可行的方案是定义一个对象表示->业务对象的映射文件,就像业务对象->数据库映射那样,这样的话应该能更好的分离表示层和业务层,有兴趣的朋友不妨试一下。