天天看點

一起談.NET技術,一句代碼實作批量資料綁定[上篇]  一、基于控件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; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Gender { get; set; }

public int? Age { get; set; }

public DateTime? BirthDay { get; set; }

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>

<td style="width:20%;text-align:right">First Name:</td>

<td><asp:TextBox ID="FirstName" runat="server"></asp:TextBox></td>

<td style="width:20%;text-align:right">Last Name:</td>

<td><asp:TextBox ID="LastName" runat="server"></asp:TextBox></td>

<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>

<td style="width:20%;text-align:right">Age:</td>

<td><asp:TextBox ID="Age" runat="server"></asp:TextBox></td>

<td style="width:20%;text-align:right">Birthday:</td>

<td><asp:TextBox ID="Birthday" runat="server" Width="313px"></asp:TextBox></td>

<td style="width:20%;text-align:right">Is VIP:</td>

<td><asp:CheckBox ID="IsVip" runat="server"></asp:CheckBox></td>

<td colspan="2" align="center">

<asp:Button ID="ButtonBind" runat="server" Text="Bind" onclick="ButtonBind_Click" />

</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按鈕,你會發現綁定的資料已經正确顯示在了對應的控件中:

一起談.NET技術,一句代碼實作批量資料綁定[上篇]  一、基于控件ID/實體屬性名映射的資料綁定  二、一句代碼實作批量資料綁定  四、過濾不需要綁定的屬性

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

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

this.DataBinder.DataItemBinding += (sender, args) =>

if (args.BindingMapping.Control == this.Birthday)

args.BindingMapping.FormatString = "MM-dd-yyyy";

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

一起談.NET技術,一句代碼實作批量資料綁定[上篇]  一、基于控件ID/實體屬性名映射的資料綁定  二、一句代碼實作批量資料綁定  四、過濾不需要綁定的屬性

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

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表示格式化字元串(實際上面我們指定的格式化字元串就是為這個屬性指定的)。那麼,我們也可以通過下面的代碼來進行資料綁定:

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:

args.BindingMapping.AutomaticBind = false;

  程式執行後,Birthday對應的TextBox将不會被綁定:

一起談.NET技術,一句代碼實作批量資料綁定[上篇]  一、基于控件ID/實體屬性名映射的資料綁定  二、一句代碼實作批量資料綁定  四、過濾不需要綁定的屬性

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

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

<td><asp:Label ID="ID_Xyz" runat="server"></asp:Label></td>

<td><asp:TextBox ID="FirstName_Xyz" runat="server"></asp:TextBox></td>

<td><asp:TextBox ID="LastName_Xyz" runat="server"></asp:TextBox></td>

<asp:RadioButtonList ID="Gender_Xyz" runat="server" RepeatDirection="Horizontal">

<td><asp:TextBox ID="Age_Xyz" runat="server"></asp:TextBox></td>

<td><asp:TextBox ID="Birthday_Xyz" runat="server" Width="313px"></asp:TextBox></td>

<td><asp:CheckBox ID="IsVip_Xyz" runat="server"></asp:CheckBox></td>

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

this.DataBinder.BindData(customer, this, "_Xyz");

  如果通過預先建立的BindingMapping集合進行資料綁定,那麼代碼将是這樣:

var bindingMappings = Artech.DataBinding.DataBinder.BuildBindingMappings(typeof(Customer), this, "_Xyz");