一句代碼實現批量數據綁定[上篇]

作者: ※森林小居※  來源: 博客園  發布時間: 2011-04-12 10:39  閱讀: 2138 次  推薦: 2   原文鏈接   [收藏]  
摘要:對于一個以數據處理為主的應用中的UI層,我們往往需要編寫相當多的代碼去實現數據綁定。如果界面上的控件和作為數據源的實體類型之間存儲某種約定的映射關系,我們就可以實現批量的數據綁定,作者開發了的插件正是用于此,本篇著重介紹如何通過這個組件來解決我們在進行數據綁定過程中的常見問題。

  對于一個以數據處理為主的應用中的UI層,我們往往需要編寫相當多的代碼去實現數據綁定。如果界面上的控件和作為數據源的實體類型之間存儲某種約定的映射關系,我們就可以實現批量的數據綁定。為了驗證這種想法,我寫了一個小小的組件。這個小玩意僅僅是我花了兩個小時寫的,其中還有很多問題沒有解決,比如對于空值的處理,特殊控件屬性值的HTML編碼問題,以及頻繁反射的性能問題,僅僅演示一種解決思路而已。本篇著重介紹如何通過這個組件來解決我們在進行數據綁定過程中的常見問題,下篇會介紹它的設計。[源代碼從這里下載]

目錄:
一、基于控件ID/實體屬性名映射的數據綁定
二、一句代碼實現批量數據綁定
三、修正綁定數據的顯示格式
四、過濾不需要綁定的屬性
五、多個控件對應同一個實體屬性

  一、基于控件ID/實體屬性名映射的數據綁定

  我的這個組件暫時命名為DataBinder好了(注意和System.Web.UI.DataBinder區分),我們用它來將一個實體對象綁定給指定的容器控件中的所有子控件。下面是DataBinder的定義,兩個BindData方法實現具體的綁定操作。

 
public class DataBinder
{

public event EventHandler<DataBindingEventArgs> DataItemBinding;
public event EventHandler<DataBindingEventArgs> DataItemBound;

public static IEnumerable<BindingMapping> BuildBindingMappings(Type entityType, Control container, string suffix = "");

public void BindData(object entity, Control container, string suffix = "");
public void BindData( object entity,IEnumerable<BindingMapping> bindingMappings);
}

  本文開頭所說,自動批量的數據綁定依賴于控件和作為數據源實體類型的映射關系。在這里,我直接采用控件ID和實體屬性名之間的映射。也就是說,在對于界面上控件進行命名的時候,應該根據對應的實體類型屬性名進行規范命名。

  另一方面,作為數據源的對象來說,它的所有屬性并不都是為數據綁定而涉及。為了讓DataBinder能夠自動篩選用于綁定的屬性,我在相應的屬性上應用了一個自定義特性:DataPropertyAttribute。比如,下面的Customer對象會在后續的演示中用到,它的每一個數據屬性都應用了這樣一個DataPropertyAttribute特性。

 
public class Cutomer
{
[DataProperty]

public string ID { get; set; }
[DataProperty]

public string FirstName { get; set; }
[DataProperty]

public string LastName { get; set; }
[DataProperty]

public string Gender { get; set; }
[DataProperty]

public int? Age { get; set; }
[DataProperty]

public DateTime? BirthDay { get; set; }
[DataProperty]

public bool? IsVip { get; set; }
}

  二、一句代碼實現批量數據綁定

  現在我們就來演示如何通過我們定義的DataBinder實現“一句代碼的數據批量綁定”,而作為數據源就是我們上面定義的Customer對象。我們先來設計我們的頁面,下面是主體部分的HTML,這是一個表格。需要注意的是:所有需要綁定到Customer對象的空間都和對應的屬性具有相同的ID。

 
<table>
<tr>
<td style="width:20%;text-align:right">ID:</td>
<td><asp:Label ID="ID" runat="server"></asp:Label></td>
</tr>
<tr>
<td style="width:20%;text-align:right">First Name:</td>
<td><asp:TextBox ID="FirstName" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Last Name:</td>
<td><asp:TextBox ID="LastName" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Gender:</td>
<td>
<asp:RadioButtonList ID="Gender" runat="server" RepeatDirection="Horizontal">
<asp:ListItem Text="Male" Value = "Male" />
<asp:ListItem Text="Female" Value = "Female" />
</asp:RadioButtonList>
</td>
</tr>
<tr>
<td style="width:20%;text-align:right">Age:</td>
<td><asp:TextBox ID="Age" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Birthday:</td>
<td><asp:TextBox ID="Birthday" runat="server" Width="313px"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Is VIP:</td>
<td><asp:CheckBox ID="IsVip" runat="server"></asp:CheckBox></td>
</tr>
<tr>
<td colspan="2" align="center">
<asp:Button ID="ButtonBind" runat="server" Text="Bind" onclick="ButtonBind_Click" />
</td>
</tr>
</table>

  為了編成方便,將DataBinder對象作為Page類型的一個屬性,該屬性在構造函數中初始化。

 
public partial class Default : System.Web.UI.Page
{

public Artech.DataBinding.DataBinder DataBinder { get; private set; }
public Default()
{

this.DataBinder = new Artech.DataBinding.DataBinder();
}
}

  然后我將數據綁定操作實現的Bind按照的Click事件中,對應所有的代碼如下所示——真正的用于數據綁定的代碼只有一句。

 
protected void ButtonBind_Click(object sender, EventArgs e)
{
var customer
= new Customer
{
ID
= Guid.NewGuid().ToString(),
FirstName
= "Zhang",
LastName
= "San",
Age
= 30,
Gender
= "Male",
BirthDay
= new DateTime(1981, 1, 1),
IsVip
= true
};
this.DataBinder.BindData(customer, this);
}

  在瀏覽器中打開該Web頁面,點擊Bind按鈕,你會發現綁定的數據已經正確顯示在了對應的控件中:

image  三、修正綁定數據的顯示格式

  雖然通過DataBinder實現了對多個控件的批量綁定,但是并不完美。一個顯著的問題是:作為生日的字段不僅僅顯示了日期,還顯示了時間。我們如何讓日期按照我們要求的格式進行顯示呢?DataBinder為了提供了三種選擇。

  如果你注意看DataBinder定義了,你會發現它定義了兩個事件:DataItemBinding和DataItemBound(命名有待商榷),它們分別在對某個控件進行綁定之前和之后觸發。我們的第一種方案就是注冊DataItemBinding時間,為Birthday指定一個格式化字符串。假設我們需要的格式是“月-日-年”,那么我們指定的格式化字符串:MM-dd-yyyy。事件注冊我方在了Page的構造函數中:

 
public Default()
{

this.DataBinder = new Artech.DataBinding.DataBinder();
this.DataBinder.DataItemBinding += (sender, args) =>
{
if (args.BindingMapping.Control == this.Birthday)
{
args.BindingMapping.FormatString
= "MM-dd-yyyy";
}
};
}

  運行程序,你會發現作為生日的字段已經按照我們希望的格式顯示出來:

image

  上面介紹了通過注冊DataItemBinding事件在綁定前指定格式化字符串的解決方案,你也可以通過注冊DataItemBound事件在綁定后修正顯示的日期格式,相應的代碼如下:

 
public Default()
{

this.DataBinder = new Artech.DataBinding.DataBinder();
this.DataBinder.DataItemBound += (sender, args) =>
{
if (args.BindingMapping.Control == this.Birthday && null != args.DataValue)
{

this.Birthday.Text = ((DateTime)Convert.ChangeType(args.DataValue, typeof(DateTime))).
ToString(
"MM-dd-yyyy");
}
};
}

  DataBinder定義了兩個BindData重載,我們使用的是通過指定數據源和容器控件的方式,而另一個重載的參數為IEnumerable<BindingMapping>類型。而BindingMapping是我們自定義的類型,用于表示控件和實體屬性之間的運行時映射關系。而這樣一個BindingMapping集合,可以通過DataBinder的靜態方法BuildBindingMappings來創建。BindingMapping具有一個FormatString表示格式化字符串(實際上面我們指定的格式化字符串就是為這個屬性指定的)。那么,我們也可以通過下面的代碼來進行數據綁定:

 
protected void ButtonBind_Click(object sender, EventArgs e)
{
var customer
= new Customer
{
ID
= Guid.NewGuid().ToString(),
FirstName
= "Zhang",
LastName
= "San",
Age
= 30,
Gender
= "Male",
BirthDay
= new DateTime(1981, 1, 1),
IsVip
= true
};
var bindingMappings
= Artech.DataBinding.DataBinder.BuildBindingMappings(typeof(Customer), this);
bindingMappings.Where(mapping
=> mapping.Control == this.Birthday).First().FormatString = "MM-dd-yyyy";
this.DataBinder.BindData(customer, bindingMappings);
}

  四、過濾不需要綁定的屬性

  在默認的情況下,第一個BindData方法(指定容器控件)會遍歷實體的所有屬性,將其綁定到對應的控件上。可能在有的時候,對于某些特殊的屬性,我們不需要進行綁定。比如,某個控件的ID雖然符合實體屬性的映射,但是它們表示的其實根本不是相同性質的數據。

  為了解決在這個問題,在BindingMapping類型中定義了一個布爾類型的AutomaticBind屬性。如果你在綁定前將該屬性設置成False,那么基于該BindingMapping的數據綁定將被忽略。如果你調用BindData(object entity, Control container, string suffix = "")這個重載,你可以通過注冊DataItemBinding事件將相應BindingMapping的AutomaticBind屬性設置成False。如果你調用BindData( object entity,IEnumerable<BindingMapping> bindingMappings)這個重載,你只需要在調用之間將相應BindingMapping的AutomaticBind屬性設置成False。

  我們將我們的程序還原成最初的狀態,現在通過注冊BindingMapping事件將基于Birthday的BindingMapping的AutomaticBind屬性設置成False:

 
public Default()
{

this.DataBinder = new Artech.DataBinding.DataBinder();
this.DataBinder.DataItemBinding += (sender, args) =>
{
if (args.BindingMapping.Control == this.Birthday)
{
args.BindingMapping.AutomaticBind
= false;
}
};
}

  程序執行后,Birthday對應的TextBox將不會被綁定:

image  五、多個控件對應同一個實體屬性

  在上面的例子中,我們的控件的ID和對應的實體屬性是相同的。但是在很多情況下,相同的頁面上有不止一個控件映射到實體的同一個屬性上。而控件ID的唯一性決定了我們不能為它們起相同的ID。在這種情況下,我們采用“基于后綴”的映射。也就是為,在為控件進行命名的時候,通過“實體屬性名+后綴”形式來指定。

  如果你仔細看了DataBinder的定義,不論是實例方法BindData(接受Control類型參數的),還是靜態方法BuildBindingMappings,都具有一個缺省參數suffix,這就是為這種情況設計的。在默認的情況下,這個參數的值為空字符串,所以我們需要控件和實體屬性具有相同的名稱。如果控件是基于“實體屬性名+后綴”來命名的,就需要顯式指定這個參數了。為了演示這種情況,我們將例子中的所有需要綁定的空間ID加上一個“_Xyz”字符作為后綴。

 
<table>
<tr>
<td style="width:20%;text-align:right">ID:</td>
<td><asp:Label ID="ID_Xyz" runat="server"></asp:Label></td>
</tr>
<tr>
<td style="width:20%;text-align:right">First Name:</td>
<td><asp:TextBox ID="FirstName_Xyz" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Last Name:</td>
<td><asp:TextBox ID="LastName_Xyz" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Gender:</td>
<td>
<asp:RadioButtonList ID="Gender_Xyz" runat="server" RepeatDirection="Horizontal">
<asp:ListItem Text="Male" Value = "Male" />
<asp:ListItem Text="Female" Value = "Female" />
</asp:RadioButtonList>
</td>
</tr>
<tr>
<td style="width:20%;text-align:right">Age:</td>
<td><asp:TextBox ID="Age_Xyz" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Birthday:</td>
<td><asp:TextBox ID="Birthday_Xyz" runat="server" Width="313px"></asp:TextBox></td>
</tr>
<tr>
<td style="width:20%;text-align:right">Is VIP:</td>
<td><asp:CheckBox ID="IsVip_Xyz" runat="server"></asp:CheckBox></td>
</tr>
<tr>
<td colspan="2" align="center">
<asp:Button ID="ButtonBind" runat="server" Text="Bind" onclick="ButtonBind_Click" />
</td>
</tr>
</table>

  如果采用指定容器控件進行直接綁定的話,就可以這樣編程:

 
protected void ButtonBind_Click(object sender, EventArgs e)
{
var customer
= new Customer
{
ID
= Guid.NewGuid().ToString(),
FirstName
= "Zhang",
LastName
= "San",
Age
= 30,
Gender
= "Male",
BirthDay
= new DateTime(1981, 1, 1),
IsVip
= true
};
this.DataBinder.BindData(customer, this, "_Xyz");
}

  如果通過預先創建的BindingMapping集合進行數據綁定,那么代碼將是這樣:

 
protected void ButtonBind_Click(object sender, EventArgs e)
{
var customer
= new Customer
{
ID
= Guid.NewGuid().ToString(),
FirstName
= "Zhang",
LastName
= "San",
Age
= 30,
Gender
= "Male",
BirthDay
= new DateTime(1981, 1, 1),
IsVip
= true
};

var bindingMappings
= Artech.DataBinding.DataBinder.BuildBindingMappings(typeof(Customer), this, "_Xyz");
this.DataBinder.BindData(customer, bindingMappings);
}
2
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()