ASP.NET进阶(7):认清控件之DataBind
数据绑定,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的一些东西,只是没有他那么庞大,但有些思路居然出奇的相似。 重复造轮子或许能让我们更容易把一些东西理解透彻。仅仅做一个代码组装工没意思,你说呢?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
2009-04-08 原创:泛型方法例子