數據綁定,databind()是多麼親切的方法,我們每天很不能敲上幾百遍。但是你有木有想過他是如何實現的?有木有!!!(咆哮體:)今天,我們拿Repeater來說說DataBind那些事兒。如果朋友你有看過我寫的模版引擎裏Label的初級應用的話,我在最後貼了一個List的Label的實現,其中有點意思的就是模仿Repeater。不過看沒看過無所謂的。今天我們看看微軟人家正兒八經的Repeater。
一般控件的綁定我們都是在Page_Load事件方法裏這麼寫
if(!IsPostBack)
{
BindList();
}
public void BindList()
{
lst.DataSource = DataHelper.GetList();
lst.DataBind();
}
第一步就是我們給DataSource成員賦值,看看賦值的時候發生了什麼?
public virtual object DataSource
{
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.dataSource;
}
set
{
if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
{
throw new ArgumentException(SR.GetString("Invalid_DataSource_Type", new object[] { this.ID }));
}
this.dataSource = value;
this.OnDataPropertyChanged();
}
}
從上面的方法中我們可以看出DataSource還真不簡單,一個賦值就幹了很多事,其實get和set是兩個方法(get_dataSource、set_dataSource),只是C#裏這麼寫比較好看些。
如果我們指定的datasource不是IEnumerable(可迭代),不是IListSource類型,則拋出異常(參數錯誤:無效的數據源),說明肯定要用到IListSource的方法,而且要循環使用(Repeater肯定的循環啊)。
然後執行了OnDataPropertyChanged()的方法。看名字就能猜到該方法就是一個地下黨,他會告訴類裏面的某些成員說:“嘿,夥計,數據源變啦,各單位注意!”,這時候Repeater去獲取數據的時候會問這個地下黨:”哥們,現在風頭緊不緊。。。“
protected virtual void OnDataPropertyChanged()
{
if (this._throwOnDataPropertyChange)
{
throw new HttpException(SR.GetString("DataBoundControl_InvalidDataPropertyChange", new object[] { this.ID }));
}
if (this._inited)
{
this.RequiresDataBinding = true;
}
this._currentViewValid = false;
}
第一句拋出異常就是”內部組織“決定殺人滅口,根本沒機會了。第二句,_inited,是否初始化過,_currentViewValid,意思是當前的DataView是否驗證過?(意思是這數據源來源不明啊)
第二句話就是執行DataBind()了,DataBind就會用到上面的”地下組織“。
public override void DataBind()
{
if ((!this.IsBoundUsingDataSourceID || !base.DesignMode) || (base.Site != null))
{
this.RequiresDataBinding = false;
this.OnDataBinding(EventArgs.Empty);
}
}
就是說是否使用DataSourceID來綁定? 顯然我們不是,我們直接給定的數據源。 如果指定了DataSourceID,在Render Repeater時會搜尋名字爲DataSourceID的DataSource控件。這是一種前臺指定數據源的方式。不過最終都是要執行DataBind。我們來看Repeater的OnDataBinding:
protected override void OnDataBinding(EventArgs e)
{
base.OnDataBinding(e);//執行了父類Control的OnDataBinding事件
this.Controls.Clear();//清空子控件
base.ClearChildViewState();//清空子控件的ViewState信息
this.CreateControlHierarchy(true);//創建子控件
base.ChildControlsCreated = true;//標記已創建
}
父類的OnDataBingding事件很簡單:
protected virtual void OnDataBinding(EventArgs e)
{
if (this.HasEvents())
{
EventHandler handler = this._events[EventDataBinding] as EventHandler;
if (handler != null)
{
handler(this, e);
}
}
}
就是查看是否訂閱了DataBingding事件方法,訂閱即執行,我們一般都是給Repeater的ItemTemplate子控件裏綁定數據的。
清空的我們就不看了,都是一些remove。我們看看CreateControlHierarchy:
/// <summary>
/// 創建子控件
/// </summary>
/// <param name="useDataSource">是否使用數據源</param>
protected virtual void CreateControlHierarchy(bool useDataSource)
{
IEnumerable data = null;//聲明數據
int dataItemCount = -1;//數據的個數
if (this.itemsArray != null)
{
this.itemsArray.Clear();//看看還有模版沒,有就清掉
}
else
{
this.itemsArray = new ArrayList();//沒有就初始化一個
}
if (!useDataSource)//如果不使用的數據源,就直接從ViewState裏初始化容量,並弄了僞數據
{
dataItemCount = (int) this.ViewState["_!ItemCount"];
if (dataItemCount != -1)
{
data = new DummyDataSource(dataItemCount);
this.itemsArray.Capacity = dataItemCount;
}
}
else
{
data = this.GetData();//如果指定了數據源,則獲取數據
ICollection is2 = data as ICollection;
if (is2 != null)
{
this.itemsArray.Capacity = is2.Count;
}
}
if (data != null)//如果數據正常
{
int itemIndex = 0;
bool flag = this.separatorTemplate != null;//是否有分隔符模版
dataItemCount = 0;
if (this.headerTemplate != null)//如果有頭部,則創建頭部
{
this.CreateItem(-1, ListItemType.Header, useDataSource, null);//顯然 -1 不算行數,類型是頭部,使用數據源,且頭部行使用的數據居然是null...
}
foreach (object obj2 in data)//遍歷數據
{
if (flag && (dataItemCount > 0))
{
this.CreateItem(itemIndex - 1, ListItemType.Separator, useDataSource, null);//分隔符
}
ListItemType itemType = ((itemIndex % 2) == 0) ? ListItemType.Item : ListItemType.AlternatingItem;
RepeaterItem item = this.CreateItem(itemIndex, itemType, useDataSource, obj2);//Item和Alter交替創建
this.itemsArray.Add(item);
dataItemCount++;
itemIndex++;
}
if (this.footerTemplate != null)
{
this.CreateItem(-1, ListItemType.Footer, useDataSource, null);//創建底部
}
}
if (useDataSource)
{
this.ViewState["_!ItemCount"] = (data != null) ? dataItemCount : -1;//給ViewState的ItemCount賦值
}
}
從上面的註釋中我們能看出就是循環創建Item。這些Item就是我們常用的HeadItemTemplate ItemTemplate等子控件啦。把數據傳給他們,讓他們自己實現數據綁定。話說我的模版引擎也這麼做的哦:),但是我真的沒抄襲他,哈哈。
其中的GetData方法就是獲取數據的:
protected virtual IEnumerable GetData()
{
DataSourceView view = this.ConnectToDataSourceView();
if (view != null)
{
return view.ExecuteSelect(this.SelectArguments);
}
return null;
}
ConnectToDataSourceView()方法比較長其主要工作就是拿數據源:
private DataSourceView ConnectToDataSourceView()
{
if (!this._currentViewValid || base.DesignMode)//數據源沒有驗證過,指定DataSource的值就會_currentViewValid = false
{
if ((this._currentView != null) && this._currentViewIsFromDataSourceID)
{
this._currentView.DataSourceViewChanged -= new EventHandler(this.OnDataSourceViewChanged);
}
IDataSource source = null;
string dataSourceID = this.DataSourceID;
if (dataSourceID.Length != 0)//如果是指定了DataSourceID的數據源,就FindControl 找到對應的DataSource控件
{
Control control = DataBoundControlHelper.FindControl(this, dataSourceID);
if (control == null)
{
throw new HttpException(SR.GetString("DataControl_DataSourceDoesntExist", new object[] { this.ID, dataSourceID }));
}
source = control as IDataSource;
if (source == null)
{
throw new HttpException(SR.GetString("DataControl_DataSourceIDMustBeDataControl", new object[] { this.ID, dataSourceID }));
}
}
if (source == null)//如果沒有從控件找到數據,就用我們指定的數據源
{
source = new ReadOnlyDataSource(this.DataSource, this.DataMember);
}
else if (this.DataSource != null)//如果兩個數據源都存在就拋出異常
{
throw new InvalidOperationException(SR.GetString("DataControl_MultipleDataSources", new object[] { this.ID }));
}
DataSourceView view = source.GetView(this.DataMember);
if (view == null)
{
throw new InvalidOperationException(SR.GetString("DataControl_ViewNotFound", new object[] { this.ID }));
}
this._currentViewIsFromDataSourceID = this.IsBoundUsingDataSourceID;
this._currentView = view;
if ((this._currentView != null) && this._currentViewIsFromDataSourceID)
{
this._currentView.DataSourceViewChanged += new EventHandler(this.OnDataSourceViewChanged);
}
this._currentViewValid = true;
}
return this._currentView;
}
我們關注下CreateItem方法,因爲實際的數據顯示地方在哪裏,這裏只是把大體框架做好。
/// <summary>
/// 創建Repeater的Item
/// </summary>
/// <param name="itemIndex">第幾行</param>
/// <param name="itemType">Item的類型</param>
/// <param name="dataBind">是否綁定數據</param>
/// <param name="dataItem">本行數據</param>
private RepeaterItem CreateItem(int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
RepeaterItem item = this.CreateItem(itemIndex, itemType);//先聲明個Item
RepeaterItemEventArgs e = new RepeaterItemEventArgs(item);//聲明個事件參數
this.InitializeItem(item);//給repeater的各種template賦值
if (dataBind)
{
item.DataItem = dataItem;//如果要綁定數據則把數據指定給DataItem屬性
}
this.OnItemCreated(e);//執行創建Item的事件(其實我們貌似都沒用過吧)
this.Controls.Add(item);//添加Item
if (dataBind)
{
item.DataBind();//正點,開始綁定數據啦~
this.OnItemDataBound(e);//執行綁定後的事件方法
item.DataItem = null;//卸掉數據,等他垃圾回收
}
return item;
}
Item的dateItem數據就是DataBinder.Eval("xxx")時需要被反射的對象。也就是每個控件當前的數據,是的,每個控件,剛纔上面我們看到repeater的整了一大堆的item。。。
DataBinder的Eval是個靜態方法,代碼非常簡單,就是靠反射獲取DataItem的值。我們可以看看他的實現:
public static object Eval(object container, string expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
expression = expression.Trim();
if (expression.Length == 0)
{
throw new ArgumentNullException("expression");
}
if (container == null)
{
return null;
}
string[] expressionParts = expression.Split(expressionPartSeparator);
return Eval(container, expressionParts);
}
private static object Eval(object container, string[] expressionParts)
{
object propertyValue = container;
for (int i = 0; (i < expressionParts.Length) && (propertyValue != null); i++)
{
string propName = expressionParts[i];
if (propName.IndexOfAny(indexExprStartChars) < 0)
{
propertyValue = GetPropertyValue(propertyValue, propName);
}
else
{
propertyValue = GetIndexedPropertyValue(propertyValue, propName);
}
}
return propertyValue;
}
public static string Eval(object container, string expression, string format)
{
object obj2 = Eval(container, expression);
if ((obj2 == null) || (obj2 == DBNull.Value))
{
return string.Empty;
}
if (string.IsNullOrEmpty(format))
{
return obj2.ToString();
}
return string.Format(format, obj2);
}
public static object GetDataItem(object container)
{
bool flag;
return GetDataItem(container, out flag);
}
看看RepeaterItem的DataBind,其實就是Control類的DataBind
public virtual void DataBind()
{
this.DataBind(true);
}
protected virtual void DataBind(bool raiseOnDataBinding)
{
bool flag = false;
if (this.IsBindingContainer)//是綁定的容器嗎
{
bool flag2;
object dataItem = DataBinder.GetDataItem(this, out flag2);//獲取該容器的DataItem,在之前賦值過的,內部的執行也是先獲取成員,獲取不到再反射。
if (flag2 && (this.Page != null))//如果獲取到了數據,並且當前Page不爲null
{
this.Page.PushDataBindingContext(dataItem);//這句我也沒弄明白用處,就是把當前綁定的數據給當前Page綁定上下文的一個棧裏,這個數據會被Page.GetDataItem()用到,所以也就是Page的Eval才能用到,可是控件綁定完畢後就被卸載了啊?我沒弄清楚用處:)
flag = true;
}
}
try
{
if (raiseOnDataBinding)
{
this.OnDataBinding(EventArgs.Empty);
}
this.DataBindChildren();//綁定子控件
}
finally
{
if (flag)
{
this.Page.PopDataBindingContext();//整個綁定完後,卸載這條數據
}
}
}
子控件的綁定其實就是整個流程的遞歸了,不管你有啥子控件,咱在讓子控件來一次DataBind(),方法如下:
protected virtual void DataBindChildren()
{
if (this.HasControls())
{
string errorMsg = this._controls.SetCollectionReadOnly("Parent_collections_readonly");
try
{
try
{
int count = this._controls.Count;
for (int i = 0; i < count; i++)
{
this._controls[i].DataBind();
}
}
finally
{
this._controls.SetCollectionReadOnly(errorMsg);
}
}
catch
{
throw;
}
}
}
這樣,整個綁定流程就是這樣,Repeater的展現其實就是他的每個Item的展現。
看了CMS模版引擎的同學,我這裏多介紹下關於嵌套的問題。我的CMS模版引擎沒有做嵌套標籤那一塊,如果做的話,也會類似這個方法,不過這個方式並沒有傳遞當前的DataItem給子控件,因爲我們會在ItemDataBounded事件方法裏給子控件的dataSource賦值。但是在模版引擎了我沒法去自定義方法,所以得傳遞DataItem進去。
感覺編程很有意思是,有時候你思來想去的東西,發現別人早已實現,而且做的異常強大,但這也是自己成長的一個過程吧。之所以分析WebForm的一些源碼,也是在寫了CMS模版後,發現很像WebForm的一些東西,只是沒有他那麼龐大,但有些思路居然出奇的相似。 重複造輪子或許能讓我們更容易把一些東西理解透徹。僅僅做一個代碼組裝工沒意思,你說呢?