转:让旧版本的数据绑定控件支持数据源控件。

  示例代码

       上篇介绍了在asp.net2.0版本下面如何简单的定义数据绑定控件。虽然DataBoundControl为我们提供了便利,我们以后可以从此类开始编写数据绑定控件。但是在2.0版本未到来之前,你已经为自己订制了一些数据绑定控件,既然2.0版本已经提供了数据源控件,你是否有想法,让你原有的控件也升级到同时支持通过设置DataSource属性和数据源控件来获取数据源,这样以后我们就可以省省工作了。这次我们就来讨论这个话题,让旧版本的数据绑定控件支持数据源控件

一.准备升级数据绑定控件

即使asp.net1.1版本的一些控件也都已经支持数据源控件了,如Repeater,BaseDataList等.但本身这些对象并不是从BaseDataBoundControl和DataBoundControl等类继承下来的,如Repeater其是从Control下继承的一个模板控件,其并不需要这么多从WebControl继承下来的属性,如果你想让它支持数据源控件,你首先会想到改变控件基类,从DataBoundControl开始,这是一个好想法,但可能有些情况下并不允许这么做。上次说到了BaseDataList和DataBoundControl,BaseDataList也支持数据源控件了,所以我认为从此类继承是完全没有问题的。另外的做法就是在不改变原有控件基类的情况下,你还是需要老老实实给原控件添加一些代码支持数据源控件。那么就开始吧.

二.具体实现

本次例子跟上篇相同,相同地方就略过了

1.定义基本成员

整个控件的实现方式跟DataBoundControl实现方式很相似,我们可以看看MSDN中,BaseDataList等基类添加了哪些元素,然后模仿着实现.如果对BaseDataBoundControl和DataBoundControl这两个类成员了解的话,你将对下面成员属性很熟悉,添加这些基本成员


(1)


       /// <summary>
        
/// 该值指示控件是否已经初始化
        
/// </summary>

        protected bool Initialized
        
{
            
get
            
{
                
return initialized;
            }

        }


       
public string DataMember
       
{
           
get
           
{
               
object member = ViewState["DataMember"];
               
if (member == null)
                   
return string.Empty;
               
else
                   
return (string)member;
           }

           
set
           
{
               ViewState[
"DataMember"= value;
               
this.OnDataPropertyChanged();
           }

       }


        
/// <summary>
        
/// 为数据绑定控件提供数据源
        
/// </summary>

       public IEnumerable DataSource
       
{
           
get
           
{
               
return dataSource;
           }

           
set
           
{
               
if ((value is IEnumerable) || (value is IListSource) || (value == null))
                   dataSource 
= value;
               
else
                   
throw new Exception("错误的数据源类型");
               OnDataPropertyChanged();
           }

       }


        
/// <summary>
        
/// 数据源控件的 ID 属性
        
/// </summary>

        [DefaultValue(""), IDReferenceProperty(typeof(DataSourceControl))]
        
public virtual string DataSourceID
        
{
            
get
            
{
                
object dataSourceID = ViewState["DataSourceID"];
                
if (dataSourceID != null)
                
{
                    
return (string)dataSourceID;
                }

                
return string.Empty;
            }

            
set
            
{
                
this.ViewState["DataSourceID"= value;
                
this.OnDataPropertyChanged();
            }

        }


        
/// <summary>
        
/// 获取是否设置 DataSourceID 属性的值
        
/// </summary>

        protected bool IsBoundUsingDataSourceID
        
{
            
get
            
{
                
return (DataSourceID.Length > 0);
            }

        }


        
/// <summary>
        
/// 是否需要绑定到其指定的数据源
        
/// </summary>

        protected bool RequiresDataBinding
        
{
            
get
            
{
                
return requiresDataBinding;
            }

            
set
            
{
                requiresDataBinding 
= value;
            }

        }


        
/// <summary>
        
/// 用于检索数据的 DataSourceSelectArguments 对象。默认为 Empty 值
        
/// </summary>

        protected DataSourceSelectArguments SelectArguments
        
{
            
get
            
{
                
if (selectArguments == null)
                
{
                    selectArguments 
= CreateDataSourceSelectArguments();
                }

                
return selectArguments;
            }

        }


(2)上面几个属性涉及到几个方法


       /// <summary>
        
/// 创建空的 DataSourceSelectArguments 对象
        
/// </summary>
        
/// <returns></returns>

        protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments()
        
{
            
return DataSourceSelectArguments.Empty;
        }


       
/// <summary>
       
/// 如果设置了 DataSourceID 属性且数据绑定控件标记为需要绑定,则调用 DataBind 方法
        
/// OnPreRender中调用
       
/// </summary>

       protected void EnsureDataBound()
       
{
           
if (RequiresDataBinding && (DataSourceID.Length > 0))
           
{
               DataBind();
           }

       }


       

       
/// <summary>
       
/// 在某一基数据源标识属性更改后,将数据绑定控件重新绑定到其数据
       
/// </summary>

       protected virtual void OnDataPropertyChanged()
       
{
           
if (initialized)
           
{
               RequiresDataBinding 
= true;
           }

           currentViewValid 
= false;
       }

上面的几个属性和方法可以一起来看看了,在更改数据源标识时都会调用OnDataPropertyChanged方法,然后到了EnsureDataBound方法(此方法在OnPreRender方法中调用)在使用数据源控件情况下自动调用DataBind方法。另外Initialized属性会在控件初始化时设置.


2.获取与数据绑定控件关联的IDataSource 接口
数据源控件实现了IDataSource接口,此接口定义了数据源最基本的元素,数据绑定控件要根据DataSourceID属性从容器中获取与其关联的 IDataSource 接口。如下实现

     // 从容器中获取DataControl
       private Control FindControl(Control control, string controlID)
        
{
            Control namingContainer 
= control;
            Control dataControl 
= null;
            
if (control != control.Page)
            
{
                
while ((dataControl == null&& (namingContainer != control.Page))
                
{
                    namingContainer 
= namingContainer.NamingContainer;
                    
if (namingContainer == null)
                    
{
                        
throw new HttpException("DataBoundControlHelper_NoNamingContainer");
                    }

                    dataControl 
= namingContainer.FindControl(controlID);
                }

                
return dataControl;
            }

            
return control.FindControl(controlID);
        }


        
/// <summary>
        
/// 检索与数据绑定控件关联的 IDataSource 接口
        
/// </summary>
        
/// <returns></returns>

       protected virtual IDataSource GetDataSource()
       
{
           
if (this.currentDataSource != null)
           
{
               
return currentDataSource;
           }


           
//获取数据源控件
           IDataSource source = null;
           
string controlID = DataSourceID;
           
if (controlID.Length != 0)
           
{
               Control control 
= FindControl(this, controlID);
               source 
= control as IDataSource;
           }

           
return source;
       }

3.获取数据源视图

第二步的实现是为此服务的

        private DataSourceView ConnectToDataSourceView()
        
{

            
if (!currentViewValid || base.DesignMode)
            
{
                
                
if ((currentView != null&& currentViewIsFromDataSourceID)
                
{
                    currentView.DataSourceViewChanged 
-= new EventHandler(this.OnDataSourceViewChanged);
                }


                
this.currentDataSource = GetDataSource();

                
//从DataSource获取数据源
                if (this.currentDataSource == null)
                
{
                    
this.currentDataSource = new ReadOnlyDataSource(DataSource, DataMember);
                }


                DataSourceView view 
= this.currentDataSource.GetView(DataMember);
                currentViewIsFromDataSourceID 
= IsBoundUsingDataSourceID;
                currentView 
= view;
                
                
if ((currentView != null&& currentViewIsFromDataSourceID)
                
{
                    currentView.DataSourceViewChanged 
+= new EventHandler(this.OnDataSourceViewChanged);
                }

                currentViewValid 
= true;
            }

            
return currentView;
        }


        
/// <summary>
        
/// 获取数据源视图
        
/// </summary>
        
/// <returns></returns>

        protected virtual DataSourceView GetData()
        
{
          
return ConnectToDataSourceView();
        }


请注意ConnectToDataSourceView方法,前后分别在移除和添加一个事件,将RequiresDataBinding属性设置为true重新绑定,然后再看中间这段代码

                if (this.currentDataSource == null)
                
{
                    
this.currentDataSource = new ReadOnlyDataSource(DataSource, DataMember);
                }

即当未使用数据源控件时,则就从ReadOnlyDataSource对象通过设置DataSource和DataMember属性来获取IDataSource 接口,然后才能获取到数据源视图.下面为ReadOnlyDataSource和ReadOnlyDataSourceView的简单实现,在此不做解释.下次再来讲这个东西


   public class ReadOnlyDataSource : IDataSource
    
{
        
        
private string _dataMember;
        
private object _dataSource;
        
private static string[] ViewNames = new string[0];

       
        
event EventHandler IDataSource.DataSourceChanged
        
{
            add
            
{
            }

            remove
            
{
            }

        }


        
        
public ReadOnlyDataSource(object dataSource, string dataMember)
        
{
            
this._dataSource = dataSource;
            
this._dataMember = dataMember;
        }


        DataSourceView IDataSource.GetView(
string viewName)
        
{
            IDataSource source 
= _dataSource as IDataSource;
            
if (source != null)
            
{
                
return source.GetView(viewName);
            }

            
return new ReadOnlyDataSourceView(thisthis._dataMember,DataSourceHelper.ResolveDataSource(this._dataSource, this._dataMember));
        }


        ICollection IDataSource.GetViewNames()
        
{
            
return ViewNames;
        }


    }


 
public class ReadOnlyDataSourceView : DataSourceView
    
{

        
private IEnumerable dataSource;

        
public ReadOnlyDataSourceView(ReadOnlyDataSource owner, string name, IEnumerable dataSource)
            : 
base(owner, name)
        
{
            
this.dataSource=dataSource ;
        }


        
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
        
{
            arguments.RaiseUnsupportedCapabilitiesError(
this);
            
return dataSource;
        }


    }


4.获取数据

接着你便可以在DataBind方法中通过获取到的数据源视图异步获取数据了,本来我们可以调用其ExecuteSelect方法的,可惜我们无法调用此方法,只好异步调用。接着的PerformDataBinding方法跟上篇实现一样。不再列出

记得在DataBind方法将RequiresDataBinding 属性设置为true


        /// <summary>
        
/// 将数据源绑定到控件
        
/// </summary>

        public override void DataBind()
        
{
            
if (!IsBoundUsingDataSourceID)
            
{
                OnDataBinding(EventArgs.Empty);
            }


            GetData().Select(CreateDataSourceSelectArguments(),
                OnDataSourceViewSelectCallback);
            RequiresDataBinding 
= false;
            MarkAsDataBound();
        }

        
private void OnDataSourceViewSelectCallback(IEnumerable retrievedData)
        
{
            
if (IsBoundUsingDataSourceID)
            
{
                OnDataBinding(EventArgs.Empty);
            }

            PerformDataBinding(retrievedData);
        }

5.重写控件生命周期事件

其中在OnPreRender方法中调用了EnsureDataBound方法,其他方法的话可以发现在很多不同情况下将RequiresDataBinding和Initialized属性设置为True.做了数据绑定的初始化工作。这里估计我也解释不清楚,大家还是了解下控件的生命周期,了解其事件的使用,再理解吧.这里可以参考jessezhao的这篇翻译


        protected override void OnInit(EventArgs e)
        
{
            
base.OnInit(e);
            
if (this.Page != null)
            
{
                
this.Page.PreLoad += new EventHandler(this.OnPagePreLoad);
                
if (!base.IsViewStateEnabled && this.Page.IsPostBack)
                
{
                    
this.RequiresDataBinding = true;
                }

            }

        }


        
private void OnPagePreLoad(object sender, EventArgs e)
        
{
            initialized 
= true;
            
if (Page != null)
            
{
                Page.PreLoad 
-= new EventHandler(OnPagePreLoad);
                
if (!Page.IsPostBack)
                
{
                    RequiresDataBinding 
= true;
                }

                
if ((Page.IsPostBack && base.IsViewStateEnabled) && (ViewState["DataBound"== null))
                
{
                    RequiresDataBinding 
= true;
                }

            }

        }


        
protected override void OnPreRender(EventArgs e)
        
{
            EnsureDataBound();
            
base.OnPreRender(e);
        }


        
protected override void OnLoad(EventArgs e)
        
{
            
this.initialized = true;
            
this.ConnectToDataSourceView();
            
if (this.Page != null && this.ViewState["DataBound"== null)
            
{
                
if (!this.Page.IsPostBack)
                
{
                    
this.RequiresDataBinding = true;
                }

                
else if (base.IsViewStateEnabled)
                
{
                    
this.RequiresDataBinding = true;
                }

            }

            
base.OnLoad(e);
        }

好了,基本代码的编写就完成了,接着你就可以通过设置DataSource属性手动绑定的形式和设置DataSourceID属性获取数据源的形式获取数据了。

这篇可以供参考,如果真要这么做的话,几乎每个原有的数据绑定控件都需要重复编写上面这么多代码。相比之下如DataBoundControl类和BaseDataList类都已经帮你完成了上面的工作,在有选择的情况下,我们当然不愿意写上面这么多的代码。所以说上面的这堆代码也只供你参考,能够使用新的基类的话,尽量使用,如果真的需要这么做的话,你就需要这么去改你的数据绑定控件。

这篇可能讲的不是很详细,大家如果真的有必要这么做的话,可以仔细看看。不足之处还请大家纠正^_^.
晚了,睡觉去了。
posted @ 2007-05-29 12:19  kagar  阅读(246)  评论(0编辑  收藏  举报