目錄: 一、基于控件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按鈕,你會發現綁定的資料已經正确顯示在了對應的控件中:
雖然通過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";
運作程式,你會發現作為生日的字段已經按照我們希望的格式顯示出來:
上面介紹了通過注冊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将不會被綁定:
在上面的例子中,我們的控件的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");