一句代碼實現(xiàn)批量數(shù)據(jù)綁定 下
《上篇》主要介紹如何通過DataBinder實現(xiàn)批量的數(shù)據(jù)綁定,以及如何解決常見的數(shù)據(jù)綁定問題,比如數(shù)據(jù)的格式化。接下來,我們主要來談?wù)凞ataBinder的設(shè)計,看看它是如何做到將作為數(shù)據(jù)源實體的屬性值綁定到界面對應(yīng)的控件上的。此外,需要特別說明一點:《上篇》中提供了DataBinder最初版本的下載,但已經(jīng)和本篇文章介紹的已經(jīng)大不一樣了。***版本的主要解決兩個主要問題:通過Expression Tree的方式進(jìn)行屬性操作(屬性賦值和取值),添加了“數(shù)據(jù)捕捉”(Data Capture)的功能,以實現(xiàn)將控件中的值賦給指定的實體。但是,這并不意味著這就是一個最終版本,這里面依然有一些問題,比如對空值的處理不不夠全面,比如在進(jìn)行數(shù)據(jù)綁定的時候,有的控件類型需要進(jìn)行HTML Encoding,等等。
目錄:
- 通過DataPropertyAttribute特性過濾實體的“數(shù)據(jù)屬性”
- Control/DataSource映射的表示:BindingMapping
- 如何建立Control/DataSource映射集合
- 通過映射集合實現(xiàn)數(shù)據(jù)綁定
- 通過映射集合實現(xiàn)數(shù)據(jù)捕捉
#p#
一、通過DataPropertyAttribute特性過濾實體的數(shù)據(jù)屬性
DataBinder在進(jìn)行數(shù)據(jù)綁定的時候,并沒有對作為數(shù)據(jù)源的對象作任何限制,也就是說任何類型的對象均可作為數(shù)據(jù)綁定的數(shù)據(jù)源??丶ㄟ@里指TextBox、Label等這樣綁定標(biāo)量數(shù)值的控件)綁定值來源于數(shù)據(jù)源實體的某個屬性。但是一個類型的屬性可能有很多,我們需要某種篩選機制將我們需要的“數(shù)據(jù)屬性”提取出來。這里我們是通過在屬性上應(yīng)用DataPropertyAttribute一個特性來實現(xiàn)的。
簡單起見,我不曾為DataPropertyAttribute定義任何屬性成員。DataPropertyAttribute中定義了一個靜態(tài)的GetDataProperties方法,得到給定實體類型的所有數(shù)據(jù)屬性的名稱。但是為了避免頻繁地對相同實體類型進(jìn)行反射,該方法對得到的屬性名稱數(shù)組進(jìn)行了緩存。
- [AttributeUsage( AttributeTargets.Property, AllowMultiple = false,Inherited = true)]
- public class DataPropertyAttribute: Attribute
- {
- private static Dictionary<Type, string[]> dataProperties = new Dictionary<Type, string[]>();
- public static string[] GetDataProperties(Type entityType)
- {
- Guard.ArgumentNotNullOrEmpty(entityType, "entityType");
- if (dataProperties.ContainsKey(entityType))
- {
- return dataProperties[entityType];
- }
- lock (typeof(DataPropertyAttribute))
- {
- if (dataProperties.ContainsKey(entityType))
- {
- return dataProperties[entityType];
- }
- var properties = (from property in entityType.GetProperties()
- where property.GetCustomAttributes(typeof(DataPropertyAttribute), true).Any()
- select property.Name).ToArray();
- dataProperties[entityType] = properties;
- return properties;
- }
- }
- }
#p#
二、Control/DataSource映射的表示:BindingMapping
不論是數(shù)據(jù)綁定(實體=〉控件),還是數(shù)據(jù)捕捉(控件=〉實體)的實現(xiàn)都建立在兩種之間存在著某種約定的映射之上,這個映射是整個DataBinder的核心所在。在這里,我定義了如下一個BindingMapping類型表示這個映射關(guān)系。
- public class BindingMapping: ICloneable
- {
- public Type DataSourceType { get; private set; }
- public Control Control { get; set; }
- public string ControlValueProperty { get; set; }
- public string DataSourceProperty { get; set; }
- public bool AutomaticBind { get; set; }
- public bool AutomaticUpdate { get; set; }
- public string FormatString { get; set; }
- public Type ControlValuePropertyType
- {
- get { return PropertyAccessor.GetPropertyType(this.Control.GetType(), this.ControlValueProperty); }
- }
- public Type DataSourcePropertyType
- {
- get { return PropertyAccessor.GetPropertyType(this.DataSourceType, this.DataSourceProperty); }
- }
- public BindingMapping(Type dataSourceType, Control control, string controlValueProperty, string dataSourceProperty)
- {
- //...
- this.DataSourceType = dataSourceType;
- this.Control = control;
- this.ControlValueProperty = controlValueProperty;
- this.DataSourceProperty = dataSourceProperty;
- this.AutomaticBind = true;
- this.AutomaticUpdate = true;
- }
- object ICloneable.Clone()
- {
- return this.Clone();
- }
- public BindingMapping Clone()
- {
- var bindingMapping = new BindingMapping(this.DataSourceType, this.Control, this.ControlValueProperty, this.DataSourceProperty);
- bindingMapping.AutomaticBind = this.AutomaticBind;
- bindingMapping.AutomaticUpdate = this.AutomaticBind;
- return bindingMapping;
- }
- }
這里我主要介紹一下各個屬性的含義:
DataSourceType:作為數(shù)據(jù)源實體的類型;
Control:需要綁定的控件;
ControlValueProperty:數(shù)據(jù)需要綁定到控件屬性的名稱,比如TextBox是Text屬性,而RadioButtonList則是SelectedValue屬性;
DataSourceProperty:實體類型中的數(shù)據(jù)屬性名稱
AutomaticBind:是否需要進(jìn)行自動綁定,通過它阻止不必要的自動數(shù)據(jù)綁定行為。默認(rèn)值為True,如果改成False,基于該條映射的綁定將被忽略;
AutomaticUpdate:是否需要進(jìn)行自動更新到數(shù)據(jù)實體中,通過它阻止不必要的自動數(shù)據(jù)捕捉行為。默認(rèn)值為True,如果改成False,基于該條映射的數(shù)據(jù)捕捉定將被忽略;
FormatString:格式化字符串;
ControlValuePropertyType:控件綁定屬性的類型,比如TextBox的綁定屬性為Text,那么ControlValuePropertyType為System.String;
DataSourcePropertyType:實體屬性類型。
需要補充一點的是:ControlValuePropertyType和DataSourcePropertyType使用到了之前定義的用于操作操作屬性的組件ProcessAccessor。BindingMapping采用了克隆模式。
#p#
三、如何建立Control/DataSource映射集合
BindingMapping表示的一個實體類型的數(shù)據(jù)屬性和具體控件之間的映射關(guān)系,而這種關(guān)系在使用過程中是以批量的方式進(jìn)行創(chuàng)建的。具體來說,我們通過指定實體類型和一個作為容器的空間,如果容器中的存在滿足映射規(guī)則的子控件,相應(yīng)的映射會被創(chuàng)建。映射的批量創(chuàng)建是通過DataBinder的靜態(tài)方法BuildBindingMappings來實現(xiàn)的。
在具體介紹BuildBindingMappings方法之前,我們需要先來討論一個相關(guān)的話題:在進(jìn)行數(shù)據(jù)綁定的時候,如何決定數(shù)據(jù)應(yīng)該賦值給控件的那個屬性。我們知道,不同的控件類型擁有不同的數(shù)據(jù)綁定屬性,比如TextBox自然是Text屬性,CheckBox則是Checked屬性。ASP.NET在定義控件類型的時候,采用了一個特殊性的特性ControlValuePropertyAttribute來表示那個屬性表示的是控件的“值”。比如TextBox和CheckBox分別是這樣定義的。
- [ControlValueProperty("Text")]
- public class TextBox : WebControl, IPostBackDataHandler, IEditableTextControl, ITextControl
- {
- //...
- }
- ControlValueProperty("Checked")]
- public class CheckBox : WebControl, IPostBackDataHandler, ICheckBoxControl
- {
- //...
- }
在這里我們直接將ControlValuePropertyAttribute中指定的名稱作為控件綁定的屬性名,即BindingMapping的ControlValueProperty屬性。該值得獲取通過如下一個GetControlValuePropertyName私有方法完成。為了避免重復(fù)反射操作,這里采用了全局緩存。
- private static string GetControlValuePropertyName(Control control)
- {
- if (null == control)
- {
- return null;
- }
- Type entityType = control.GetType();
- if (controlValueProperties.ContainsKey(entityType))
- {
- return controlValueProperties[entityType];
- }
- lock (typeof(DataBinder))
- {
- if (controlValueProperties.ContainsKey(entityType))
- {
- return controlValueProperties[entityType];
- }
- ControlValuePropertyAttribute controlValuePropertyAttribute = (ControlValuePropertyAttribute)entityType.GetCustomAttributes(typeof(ControlValuePropertyAttribute), true)[0];
- controlValueProperties[entityType] = controlValuePropertyAttribute.Name;
- return controlValuePropertyAttribute.Name;
- }
- }
最終的映射通過如下定義的BuildBindingMappings方法來建立,缺省參數(shù)suffix代表的是控件的后綴,其中已經(jīng)在《上篇》介紹過了。
- public static IEnumerable<BindingMapping> BuildBindingMappings(Type entityType, Control container, string suffix = "")
- {
- //...
- suffixsuffix = suffix??string.Empty;
- return (from property in DataPropertyAttribute.GetDataProperties(entityType)
- let control = container.FindControl(string.Format("{1}{0}", suffix, property))
- let controlValueProperty = GetControlValuePropertyName(control)
- where null != control
- select new BindingMapping(entityType, control, controlValueProperty, property)).ToArray();
- }
#p#
四、通過映射集合實現(xiàn)數(shù)據(jù)綁定
通過《上篇》我們知道,DataBinder提供兩種數(shù)據(jù)綁定方式:一種是直接通過傳入數(shù)據(jù)實體對象和容器控件對具有匹配關(guān)系的所有子控件進(jìn)行綁定;另外一種則是通過調(diào)用上面BuildBindingMappings靜態(tài)方法建立的BindingMapping集合,然后再借助于這個集合進(jìn)行數(shù)據(jù)綁定。這兩種方式的數(shù)據(jù)綁定對應(yīng)于如下兩個重載的BindData方法:
- public class DataBinder
- {
- //...
- public void BindData(object entity, Control container, string suffix = "");
- public void BindData(object entity,IEnumerable<BindingMapping> bindingMappings);
- }
已經(jīng)上在內(nèi)部,上面一個方法也是需要通過調(diào)用BuildBindingMappings來建立映射。數(shù)據(jù)綁定始終是根據(jù)BindingMapping集合進(jìn)行的。由于在BindingMapping中已經(jīng)定義了完成數(shù)據(jù)綁定所需的必要信息,數(shù)據(jù)綁定的邏輯變得很簡單。具體來說,數(shù)據(jù)綁定的邏輯是這樣的:遍歷所有的集合中每個BindingMapping,根據(jù)DataSourceProperty得到屬性名稱,然后進(jìn)一步從數(shù)據(jù)源實體中得到具體的值。根據(jù)ControlValuePropertyType得到目標(biāo)控件綁定屬性的類型,然后將之前得到的值轉(zhuǎn)換成該類型。***,通過ControlValueProperty得到控件的綁定屬性,將之前經(jīng)過轉(zhuǎn)換的值給控件的這個屬性就可以了。整個數(shù)據(jù)綁定實現(xiàn)在如下一個OnBindData方法中。關(guān)于屬性操作則借助于PropertyAccessor這個組件。
- protected virtual void OnBindData(IEnumerable<BindingMapping> bindingMappings, object entity)
- {
- foreach (var mapping in bindingMappings)
- {
- var bindingMapping = mapping.Clone();
- object value = PropertyAccessor.Get(entity, bindingMapping.DataSourceProperty);
- if (null != this.DataItemBinding)
- {
- var args = new DataBindingEventArgs(bindingMapping, value);
- this.DataItemBinding(this, args);
- value = args.DataValue;
- }
- if (!bindingMapping.AutomaticBind)
- {
- continue;
- }
- if (!string.IsNullOrEmpty(bindingMapping.FormatString))
- {
- value = Format(value, bindingMapping.FormatString);
- }
- Type controlValuePropertyType = PropertyAccessor.GetPropertyType(bindingMapping.Control.GetType(), bindingMapping.ControlValueProperty);
- value = ChangeType(value, controlValuePropertyType);
- if (null == value && typeof(ValueType).IsAssignableFrom(controlValuePropertyType))
- {
- value = Activator.CreateInstance(controlValuePropertyType);
- }
- PropertyAccessor.Set(bindingMapping.Control, bindingMapping.ControlValueProperty, value);
- if (null != this.DataItemBound)
- {
- this.DataItemBound(this, new DataBindingEventArgs(bindingMapping, value));
- }
- }
- }
DataBinder設(shè)計的目標(biāo)是讓默認(rèn)的綁定行為解決80%的問題,并且提供給相應(yīng)的方式去解決余下的問題。為了讓開發(fā)者能夠有效解決余下的這20%的綁定問題,我們定義兩個事件:DataItemBinding和DataBound,它們分別在進(jìn)行綁定之前和之后被觸發(fā)。關(guān)于事件的觸發(fā),已經(jīng)體現(xiàn)在OnBindData方法的定義中了。
#p#
五、通過映射集合實現(xiàn)數(shù)據(jù)捕捉
數(shù)據(jù)綁定使用到的實際上是Entity-〉Control映射,如果我們借助控件到Control-〉Entity,就能實現(xiàn)自動捕獲控件的值然后將其保存到給定的實體對象上。我為此在DataBinder上定義了兩個重載的UpdateData方法。
- public class DataBinder
- {
- //...
- public void BindData( object entity,IEnumerable<BindingMapping> bindingMappings);
- public void UpdateData( object entity, Control container, string suffix = "");
- }
UpdateData方法的實現(xiàn)和BindData方法的邏輯基本一致,將Control和Entity呼喚一下而已,所以在這里我就不再贅言敘述了。
原文鏈接:http://www.cnblogs.com/artech/archive/2011/03/27/databinding2.html
【編輯推薦】
- DBA應(yīng)用技巧:如何升級InnoDB Plugin
- 一句代碼實現(xiàn)批量數(shù)據(jù)綁定[上篇]
- DBA必備:MySQL數(shù)據(jù)庫常用操作和技巧
- MySQL日志操作教程:DBA們管理的利器
- MySQL觸發(fā)器如何正確使用