天天看點

一句“.NET技術”代碼實作批量資料綁定[下篇]  一、通過DataPropertyAttribute特性過濾實體的資料屬性  二、Control/DataSource映射的表示:BindingMapping  三、如何建立Control/DataSource映射集合  四、通過映射集合實作資料綁定  五、通過映射集合實作資料捕捉

  《上篇》主要介紹如何通過DataBinder實作批量的資料綁定,以及如何解決常見的資料綁定問題,比如資料的格式化。接下來,我們主要來談談DataBinder的設計,看看它是如何做到将作為資料源實體的屬性值綁定到界面對應的控件上的。此外,需要特别說明一點:《上篇》中提供了DataBinder最初版本的下載下傳,但已經和本篇文章介紹的已經大不一樣了。

目錄: 一、通過DataPropertyAttribute特性過濾實體的“資料屬性” 二、Control/DataSource映射的表示:BindingMapping 三、如何建立Control/DataSource映射集合 四、通過映射集合實作資料綁定 五、通過映射集合實作資料捕捉

  DataBinder在進行資料綁定的時候,并沒有對作為資料源的對象作任何限制,也就是說任何類型的對象均可作為資料綁定的資料源。控件(這裡指TextBox、Label等這樣綁定标量數值的控件)綁定值來源于資料源實體的某個屬性。但是一個類型的屬性可能有很多,我們需要某種篩選機制将我們需要的“資料屬性”提取出來。這裡我們是通過在屬性上應用DataPropertyAttribute一個特性來實作的。

  簡單起見,我不曾為DataPropertyAttribute定義任何屬性成員。DataPropertyAttribute中定義了一個靜态的GetDataProperties方法,得到給定實體類型的所有資料屬性的名稱。但是為了避免頻繁地對相同實體類型進行反射,該方法對得到的屬性名稱數組進行了緩存。

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

var properties = (from property in entityType.GetProperties()

where property.GetCustomAttributes(typeof(DataPropertyAttribute), true).Any()

select property.Name).ToArray();

dataProperties[entityType] = properties;

return properties;

  不論是資料綁定(實體=〉控件),還是資料捕捉(控件=〉實體)的實作都建立在兩種之間存在着某種約定的映射之上,這個映射是整個DataBinder的核心所在。在這裡,我定義了如下一個BindingMapping類型表示這個映射關系。

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:作為資料源實體的類型;

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分别是這樣定義的。

[ControlValueProperty("Text")]

public class TextBox : WebControl, IPostBackDataHandler, IEditableTextControl, ITextControl

ControlValueProperty("Checked")]

public class CheckBox : WebControl, IPostBackDataHandler, ICheckBoxControl

  在這裡我們直接将ControlValuePropertyAttribute中指定的名稱作為控件綁定的屬性名,即BindingMapping的ControlValueProperty屬性。該值得擷取通過如下一個GetControlValuePropertyName私有方法完成。為了避免重複反射操作,這裡采用了全局緩存。

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

ControlValuePropertyAttribute controlValuePropertyAttribute = (ControlValuePropertyAttribute)entityType.GetCustomAttributes(typeof(ControlValuePropertyAttribute), true)[0];

controlValueProperties[entityType] = controlValuePropertyAttribute.Name;

return controlValuePropertyAttribute.Name;

  最終的映射通過如下定義的BuildBindingMappings方法來建立,預設參數suffix代表的是控件的字尾,其中已經在《上篇》介紹過了。

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

suffix = 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();

public class DataBinder

public void BindData(object entity, Control container, string suffix = "");

  已經上在内部,上面一個方法也是需要通過調用BuildBindingMappings來建立映射。資料綁定始終是根據BindingMapping集合進行的。由于在BindingMapping中已經定義了完成資料綁定所需的必要資訊,資料綁定的邏輯變得很簡單。具體來說,資料綁定的邏輯是這樣的:周遊所有的集合中每個BindingMapping,根據DataSourceProperty得到屬性名稱,然後進一步從資料源實體中得到具體的值。

  根據ControlValuePropertyType得到目标控件綁定屬性的類型,然後将之前得到的值轉換成該類型。最後,通過ControlValueProperty得到控件的綁定屬性,将之前經過轉換的值給控件的這個屬性就可以了。整個資料綁定實作在如下一個OnBindData方法中。關于屬性操作則借助于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設計的目标是讓預設的綁定行為解決80%的問題,并且提供給相應的方式去解決餘下的問題。為了讓開發者能夠有效解決餘下的這20%的綁定問題,我們定義兩個事件:DataItemBinding和DataBound,它們分别在進行綁定之前和之後被觸發。關于事件的觸發,已經展現在OnBindData方法的定義中了。

  資料綁定使用到的實際上是Entity-〉Control映射,如果我們借助控件到Control-〉Entity,就能實作自動捕獲控件的值然後将其儲存到給定的實體對象上。我為此在DataBinder上定義了兩個重載的UpdateData方法。

public void BindData( object entity,IEnumerable<BindingMapping> bindingMappings);

public void UpdateData( object entity, Control container, string suffix = "");

  UpdateData方法的實作和BindData方法的邏輯基本一緻,将Control和Entity呼喚一下而已,是以在這裡我就不再贅言叙述了。

繼續閱讀