asp.net控件開發基礎(21)
上篇介紹了在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>
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;
}
}
/// 創建空的 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 接口。如下實現
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.獲取數據源視圖
第二步的實現是為此服務的
{
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重新綁定,然后再看中間這段代碼
{
this.currentDataSource = new ReadOnlyDataSource(DataSource, DataMember);
}
即當未使用數據源控件時,則就從ReadOnlyDataSource對象通過設置DataSource和DataMember屬性來獲取IDataSource 接口,然后才能獲取到數據源視圖.下面為ReadOnlyDataSource和ReadOnlyDataSourceView的簡單實現,在此不做解釋.下次再來講這個東西。
{
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(this, this._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>
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的這篇翻譯。
{
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類都已經幫你完成了上面的工作,在有選擇的情況下,我們當然不愿意寫上面這么多的代碼。所以說上面的這堆代碼也只供你參考,能夠使用新的基類的話,盡量使用,如果真的需要這么做的話,你就需要這么去改你的數據綁定控件。
下一篇:asp.net控件開發基礎(22)