一句代碼實現批量數據綁定[下篇]

作者: Artech  來源: 博客園  發布時間: 2011-03-30 13:40  閱讀: 843 次  推薦: 1   原文鏈接   [收藏]  

  《上篇》主要介紹如何通過DataBinder實現批量的數據綁定,以及如何解決常見的數據綁定問題,比如數據的格式化。接下來,我們主要來談談DataBinder的設計,看看它是如何做到將作為數據源實體的屬性值綁定到界面對應的控件上的。此外,需要特別說明一點:《上篇》中提供了DataBinder最初版本的下載,但已經和本篇文章介紹的已經大不一樣了。最新版本的主要解決兩個主要問題:通過Expression Tree的方式進行屬性操作(屬性賦值和取值),添加了“數據捕捉”(Data Capture)的功能,以實現將控件中的值賦給指定的實體。但是,這并不意味著這就是一個最終版本,這里面依然有一些問題,比如對空值的處理不不夠全面,比如在進行數據綁定的時候,有的控件類型需要進行HTML Encoding,等等。[源代碼從這里下載]

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

  一、通過DataPropertyAttribute特性過濾實體的數據屬性

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

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

  二、Control/DataSource映射的表示:BindingMapping

  不論是數據綁定(實體=〉控件),還是數據捕捉(控件=〉實體)的實現都建立在兩種之間存在著某種約定的映射之上,這個映射是整個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采用了克隆模式。

  三、如何建立Control/DataSource映射集合

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

if (controlValueProperties.ContainsKey(entityType))
{

return controlValueProperties[entityType];
}
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();
}

  四、通過映射集合實現數據綁定

  通過《上篇》我們知道,DataBinder提供兩種數據綁定方式:一種是直接通過傳入數據實體對象和容器控件對具有匹配關系的所有子控件進行綁定;另外一種則是通過調用上面BuildBindingMappings靜態方法建立的BindingMapping集合,然后再借助于這個集合進行數據綁定。這兩種方式的數據綁定對應于如下兩個重載的BindData方法:

 
public class DataBinder
{

//...
public void BindData(object entity, Control container, string suffix = "");
public void BindData(object entity,IEnumerable<BindingMapping> bindingMappings);
}

  已經上在內部,上面一個方法也是需要通過調用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 class DataBinder
{

//...
public void BindData( object entity,IEnumerable<BindingMapping> bindingMappings);
public void UpdateData( object entity, Control container, string suffix = "");
}

  UpdateData方法的實現和BindData方法的邏輯基本一致,將Control和Entity呼喚一下而已,所以在這里我就不再贅言敘述了。

1
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()