ASP.NET进阶(7):认清控件之DataBind

    数据绑定,databind()是多么亲切的方法,我们每天很不能敲上几百遍。但是你有木有想过他是如何实现的?有木有!!!(咆哮体:)今天,我们拿Repeater来说说DataBind那些事儿。如果朋友你有看过我写的模版引擎里Label的初级应用的话,我在最后贴了一个List的Label的实现,其中有点意思的就是模仿Repeater。不过看没看过无所谓的。今天我们看看微软人家正儿八经的Repeater。
    一般控件的绑定我们都是在Page_Load事件方法里这么写

if(!IsPostBack)
{
    BindList();
}
而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的一些东西,只是没有他那么庞大,但有些思路居然出奇的相似。 重复造轮子或许能让我们更容易把一些东西理解透彻。仅仅做一个代码组装工没意思,你说呢?

posted @   君之蘭  阅读(3679)  评论(3编辑  收藏  举报
编辑推荐:
· 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 原创:泛型方法例子
点击右上角即可分享
微信分享提示