一句代碼實現批量數據綁定[下篇]
《上篇》主要介紹如何通過DataBinder實現批量的數據綁定,以及如何解決常見的數據綁定問題,比如數據的格式化。接下來,我們主要來談談DataBinder的設計,看看它是如何做到將作為數據源實體的屬性值綁定到界麵對應的控件上的。此外,需要特別說明一點:《上篇》中提供了DataBinder最初版本的下載,但已經和本篇文章介紹的已經大不一樣了。最新版本的主要解決兩個主要問題:通過Expression Tree的方式進行屬性操作(屬性賦值和取值),添加了“數據捕捉”(Data Capture)的功能,以實現將控件中的值賦給指定的實體。但是,這並不意味著這就是一個最終版本,這裏麵依然有一些問題,比如對空值的處理不不夠全麵,比如在進行數據綁定的時候,有的控件類型需要進行HTML Encoding,等等。[源代碼從這裏下載]
目錄:
一、通過DataPropertyAttribute特性過濾實體的“數據屬性”
二、Control/DataSource映射的表示:BindingMapping
三、如何建立Control/DataSource映射集合
四、通過映射集合實現數據綁定
五、通過映射集合實現數據捕捉
DataBinder在進行數據綁定的時候,並沒有對作為數據源的對象作任何限製,也就是說任何類型的對象均可作為數據綁定的數據源。控件(這裏指TextBox、Label等這樣綁定標量數值的控件)綁定值來源於數據源實體的某個屬性。但是一個類型的屬性可能有很多,我們需要某種篩選機製將我們需要的“數據屬性”提取出來。這裏我們是通過在屬性上應用DataPropertyAttribute一個特性來實現的。
簡單起見,我不曾為DataPropertyAttribute定義任何屬性成員。DataPropertyAttribute中定義了一個靜態的GetDataProperties方法,得到給定實體類型的所有數據屬性的名稱。但是為了避免頻繁地對相同實體類型進行反射,該方法對得到的屬性名稱數組進行了緩存。
1: [AttributeUsage( AttributeTargets.Property, AllowMultiple = false,Inherited = true)]
2: public class DataPropertyAttribute: Attribute
3: {
4: private static Dictionary<Type, string[]> dataProperties = new Dictionary<Type, string[]>();
5: public static string[] GetDataProperties(Type entityType)
6: {
7: Guard.ArgumentNotNullOrEmpty(entityType, "entityType");
8: if (dataProperties.ContainsKey(entityType))
9: {
10: return dataProperties[entityType];
11: }
12: lock (typeof(DataPropertyAttribute))
13: {
14: if (dataProperties.ContainsKey(entityType))
15: {
16: return dataProperties[entityType];
17: }
18: var properties = (from property in entityType.GetProperties()
19: where property.GetCustomAttributes(typeof(DataPropertyAttribute), true).Any()
20: select property.Name).ToArray();
21: dataProperties[entityType] = properties;
22: return properties;
23: }
24: }
25: }
不論是數據綁定(實體=〉控件),還是數據捕捉(控件=〉實體)的實現都建立在兩種之間存在著某種約定的映射之上,這個映射是整個DataBinder的核心所在。在這裏,我定義了如下一個BindingMapping類型表示這個映射關係。
1: public class BindingMapping: ICloneable
2: {
3: public Type DataSourceType { get; private set; }
4: public Control Control { get; set; }
5: public string ControlValueProperty { get; set; }
6: public string DataSourceProperty { get; set; }
7: public bool AutomaticBind { get; set; }
8: public bool AutomaticUpdate { get; set; }
9: public string FormatString { get; set; }
10: public Type ControlValuePropertyType
11: {
12: get { return PropertyAccessor.GetPropertyType(this.Control.GetType(), this.ControlValueProperty); }
13: }
14: public Type DataSourcePropertyType
15: {
16: get { return PropertyAccessor.GetPropertyType(this.DataSourceType, this.DataSourceProperty); }
17: }
18:
19: public BindingMapping(Type dataSourceType, Control control, string controlValueProperty, string dataSourceProperty)
20: {
21: //...
22: this.DataSourceType = dataSourceType;
23: this.Control = control;
24: this.ControlValueProperty = controlValueProperty;
25: this.DataSourceProperty = dataSourceProperty;
26: this.AutomaticBind = true;
27: this.AutomaticUpdate = true;
28: }
29: object ICloneable.Clone()
30: {
31: return this.Clone();
32: }
33: public BindingMapping Clone()
34: {
35: var bindingMapping = new BindingMapping(this.DataSourceType, this.Control, this.ControlValueProperty, this.DataSourceProperty);
36: bindingMapping.AutomaticBind = this.AutomaticBind;
37: bindingMapping.AutomaticUpdate = this.AutomaticBind;
38: return bindingMapping;
39: }
40: }
這裏我主要介紹一下各個屬性的含義:
- DataSourceType:作為數據源實體的類型;
- Control:需要綁定的控件;
- ControlValueProperty:數據需要綁定到控件屬性的名稱,比如TextBox是Text屬性,而RadioButtonList則是SelectedValue屬性;
- DataSourceProperty:實體類型中的數據屬性名稱
- AutomaticBind:是否需要進行自動綁定,通過它阻止不必要的自動數據綁定行為。默認值為True,如果改成False,基於該條映射的綁定將被忽略;
- AutomaticUpdate:是否需要進行自動更新到數據實體中,通過它阻止不必要的自動數據捕捉行為。默認值為True,如果改成False,基於該條映射的數據捕捉定將被忽略;
- FormatString:格式化字符串;
- ControlValuePropertyType:控件綁定屬性的類型,比如TextBox的綁定屬性為Text,那麼ControlValuePropertyType為System.String;
- DataSourcePropertyType:實體屬性類型。
需要補充一點的是:ControlValuePropertyType和DataSourcePropertyType使用到了之前定義的用於操作操作屬性的組件ProcessAccessor。BindingMapping采用了克隆模式。
BindingMapping表示的一個實體類型的數據屬性和具體控件之間的映射關係,而這種關係在使用過程中是以批量的方式進行創建的。具體來說,我們通過指定實體類型和一個作為容器的空間,如果容器中的存在滿足映射規則的子控件,相應的映射會被創建。映射的批量創建是通過DataBinder的靜態方法BuildBindingMappings來實現的。
在具體介紹BuildBindingMappings方法之前,我們需要先來討論一個相關的話題:在進行數據綁定的時候,如何決定數據應該賦值給控件的那個屬性。我們知道,不同的控件類型擁有不同的數據綁定屬性,比如TextBox自然是Text屬性,CheckBox則是Checked屬性。ASP.NET在定義控件類型的時候,采用了一個特殊性的特性ControlValuePropertyAttribute來表示那個屬性表示的是控件的“值”。比如TextBox和CheckBox分別是這樣定義的。
1: [ControlValueProperty("Text")]
2: public class TextBox : WebControl, IPostBackDataHandler, IEditableTextControl, ITextControl
3: {
4: //...
5: }
6:
7: ControlValueProperty("Checked")]
8: public class CheckBox : WebControl, IPostBackDataHandler, ICheckBoxControl
9: {
10: //...
11: }
在這裏我們直接將ControlValuePropertyAttribute中指定的名稱作為控件綁定的屬性名,即BindingMapping的ControlValueProperty屬性。該值得獲取通過如下一個GetControlValuePropertyName私有方法完成。為了避免重複反射操作,這裏采用了全局緩存。
1: private static string GetControlValuePropertyName(Control control)
2: {
3: if (null == control)
4: {
5: return null;
6: }
7: Type entityType = control.GetType();
8: if (controlValueProperties.ContainsKey(entityType))
9: {
10: return controlValueProperties[entityType];
11: }
12: lock (typeof(DataBinder))
13: {
14: if (controlValueProperties.ContainsKey(entityType))
15: {
16: return controlValueProperties[entityType];
17: }
18: ControlValuePropertyAttribute controlValuePropertyAttribute = (ControlValuePropertyAttribute)entityType.GetCustomAttributes(typeof(ControlValuePropertyAttribute), true)[0];
19: controlValueProperties[entityType] = controlValuePropertyAttribute.Name;
20: return controlValuePropertyAttribute.Name;
21: }
22: }
最終的映射通過如下定義的BuildBindingMappings方法來建立,缺省參數suffix代表的是控件的後綴,其中已經在《上篇》介紹過了。
1: public static IEnumerable<BindingMapping> BuildBindingMappings(Type entityType, Control container, string suffix = "")
2: {
3: //...
4: suffix = suffix??string.Empty;
5: return (from property in DataPropertyAttribute.GetDataProperties(entityType)
6: let control = container.FindControl(string.Format("{1}{0}", suffix, property))
7: let controlValueProperty = GetControlValuePropertyName(control)
8: where null != control
9: select new BindingMapping(entityType, control, controlValueProperty, property)).ToArray();
10: }
通過《上篇》我們知道,DataBinder提供兩種數據綁定方式:一種是直接通過傳入數據實體對象和容器控件對具有匹配關係的所有子控件進行綁定;另外一種則是通過調用上麵BuildBindingMappings靜態方法建立的BindingMapping集合,然後再借助於這個集合進行數據綁定。這兩種方式的數據綁定對應於如下兩個重載的BindData方法:
1: public class DataBinder
2: {
3: //...
4: public void BindData(object entity, Control container, string suffix = "");
5: public void BindData(object entity,IEnumerable<BindingMapping> bindingMappings);
6: }
已經上在內部,上麵一個方法也是需要通過調用BuildBindingMappings來建立映射。數據綁定始終是根據BindingMapping集合進行的。由於在BindingMapping中已經定義了完成數據綁定所需的必要信息,數據綁定的邏輯變得很簡單。具體來說,數據綁定的邏輯是這樣的:遍曆所有的集合中每個BindingMapping,根據DataSourceProperty得到屬性名稱,然後進一步從數據源實體中得到具體的值。根據ControlValuePropertyType得到目標控件綁定屬性的類型,然後將之前得到的值轉換成該類型。最後,通過ControlValueProperty得到控件的綁定屬性,將之前經過轉換的值給控件的這個屬性就可以了。整個數據綁定實現在如下一個OnBindData方法中。關於屬性操作則借助於PropertyAccessor這個組件。
1: protected virtual void OnBindData(IEnumerable<BindingMapping> bindingMappings, object entity)
2: {
3: foreach (var mapping in bindingMappings)
4: {
5: var bindingMapping = mapping.Clone();
6: object value = PropertyAccessor.Get(entity, bindingMapping.DataSourceProperty);
7: if (null != this.DataItemBinding)
8: {
9: var args = new DataBindingEventArgs(bindingMapping, value);
10: this.DataItemBinding(this, args);
11: value = args.DataValue;
12: }
13: if (!bindingMapping.AutomaticBind)
14: {
15: continue;
16: }
17:
18: if (!string.IsNullOrEmpty(bindingMapping.FormatString))
19: {
20: value = Format(value, bindingMapping.FormatString);
21: }
22:
23: Type controlValuePropertyType = PropertyAccessor.GetPropertyType(bindingMapping.Control.GetType(), bindingMapping.ControlValueProperty);
24: value = ChangeType(value, controlValuePropertyType);
25: if (null == value && typeof(ValueType).IsAssignableFrom(controlValuePropertyType))
26: {
27: value = Activator.CreateInstance(controlValuePropertyType);
28: }
29: PropertyAccessor.Set(bindingMapping.Control, bindingMapping.ControlValueProperty, value);
30: if (null != this.DataItemBound)
31: {
32: this.DataItemBound(this, new DataBindingEventArgs(bindingMapping, value));
33: }
34: }
35: }
DataBinder設計的目標是讓默認的綁定行為解決80%的問題,並且提供給相應的方式去解決餘下的問題。為了讓開發者能夠有效解決餘下的這20%的綁定問題,我們定義兩個事件:DataItemBinding和DataBound,它們分別在進行綁定之前和之後被觸發。關於事件的觸發,已經體現在OnBindData方法的定義中了。
數據綁定使用到的實際上是Entity-〉Control映射,如果我們借助控件到Control-〉Entity,就能實現自動捕獲控件的值然後將其保存到給定的實體對象上。我為此在DataBinder上定義了兩個重載的UpdateData方法。
1: public class DataBinder
2: {
3: //...
4: public void BindData( object entity,IEnumerable<BindingMapping> bindingMappings);
5: public void UpdateData( object entity, Control container, string suffix = "");
6: }
UpdateData方法的實現和BindData方法的邏輯基本一致,將Control和Entity唿喚一下而已,所以在這裏我就不再贅言敘述了。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 11:04:28