ASP.NET 1.x 時代開發的自訂資料綁定控件 (繼承 BaseDataList 或 Control) 僅支援 DataSource 屬性 + DataBind() 模式。ASP.NET 2.0 引入資料源控件 (SqlDataSource 等) 後,新架構採用 DataSourceID 宣告式繫結,自動處理資料查詢與重新綁定。如何在不重寫控件邏輯的前提下,讓舊版控件支援新架構?本篇提供完整遷移方案。
| 特性 | 舊模式 (1.x) | 新模式 (2.0+) |
|---|---|---|
| 資料來源指定 | DataSource 屬性指派物件 | DataSourceID 指定資料源控件 ID |
| 繫結觸發 | 手動呼叫 DataBind() | 自動於 PreRender 階段觸發 |
| 資料查詢 | 開發者自行撰寫 | 資料源控件封裝 |
| 分頁/排序 | 自行實作 | 資料源控件自動支援 |
| 狀態管理 | 手動處理 | 框架自動管理 |
public interface IDataBoundControl
{
string DataSourceID { get; set; }
IDataSource DataSourceObject { get; }
DataSourceView DataSourceView { get; }
void DataBind();
}
public interface IDataBoundListControl : IDataBoundControl
{
string DataMember { get; set; }
DataSourceSelectArguments SelectArguments { get; }
}
為支援 DataSourceID,控件必須實作以下核心成員 (參考 BaseDataBoundControl):
// 是否已初始化
protected bool Initialized { get; private set; }
// 資料成員 (DataSet 多表時指定表名)
public string DataMember
{
get => ViewState["DataMember"] as string ?? String.Empty;
set
{
ViewState["DataMember"] = value;
OnDataPropertyChanged();
}
}
// 直接指派資料源 (舊模式相容)
public IEnumerable DataSource
{
get => _dataSource;
set
{
if (value is IEnumerable || value is IListSource || value == null)
_dataSource = value;
else
throw new ArgumentException("資料源必須實作 IEnumerable 或 IListSource");
OnDataPropertyChanged();
}
}
private IEnumerable _dataSource;
// 資料源控件 ID (新模式核心)
[DefaultValue(""), IDReferenceProperty(typeof(DataSourceControl))]
public virtual string DataSourceID
{
get => ViewState["DataSourceID"] as string ?? String.Empty;
set
{
ViewState["DataSourceID"] = value;
OnDataPropertyChanged();
}
}
// 是否使用 DataSourceID 模式
protected bool IsBoundUsingDataSourceID => DataSourceID.Length > 0;
// 是否需要繫結資料
protected bool RequiresDataBinding
{
get => _requiresDataBinding;
set => _requiresDataBinding = value;
}
private bool _requiresDataBinding;
// 查詢參數
protected DataSourceSelectArguments SelectArguments
{
get
{
if (_selectArguments == null)
_selectArguments = CreateDataSourceSelectArguments();
return _selectArguments;
}
}
private DataSourceSelectArguments _selectArguments;
// 取得資料源控件實例
protected IDataSource GetDataSource()
{
if (IsBoundUsingDataSourceID)
{
Control control = FindControl(DataSourceID);
if (control == null)
throw new HttpException($"找不到 ID 為 '{DataSourceID}' 的資料源控件");
return control as IDataSource ??
throw new HttpException($"控件 '{DataSourceID}' 非資料源控件");
}
return null;
}
// 取得資料源視圖
protected DataSourceView GetDataSourceView()
{
IDataSource dataSource = GetDataSource();
return dataSource?.GetView(String.Empty);
}
// 核心繫結邏輯
protected override void PerformDataBinding(IEnumerable data)
{
// 1. 如果使用 DataSourceID,從視圖取得資料
if (IsBoundUsingDataSourceID)
{
DataSourceView view = GetDataSourceView();
if (view != null)
{
// 這裡會自動觸發 Selecting/Selected 事件
data = view.ExecuteSelect(SelectArguments);
}
}
// 2. 呼叫子類別實作的實際繫結邏輯
if (data != null)
{
PerformSelect(data);
}
// 3. 重設繫結需求旗標
RequiresDataBinding = false;
Initialized = true;
}
// 子類別必須實作:實際將資料繫結到 UI
protected abstract void PerformSelect(IEnumerable data);
// 資料屬性變更通知
protected virtual void OnDataPropertyChanged()
{
if (Initialized)
RequiresDataBinding = true;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// 關鍵:PreRender 階段自動執行繫結
if (RequiresDataBinding)
{
EnsureDataBound();
}
}
public override void DataBind()
{
// 支援手動呼叫 DataBind() (舊模式相容)
RequiresDataBinding = true;
EnsureDataBound();
base.DataBind(); // 呼叫 Control.DataBind() 觸發子控件繫結
}
protected void EnsureDataBound()
{
if (RequiresDataBinding && !IsBoundUsingDataSourceID && DataSource == null)
return; // 無資料源則跳過
PerformDataBinding(null); // null 表示從 DataSourceView 取得
MarkAsDataBound();
}
protected virtual void MarkAsDataBound()
{
// 子類別可覆寫標記已繫結狀態
}
public class LegacyRepeater : BaseDataList
{
// 1.x 風格:僅支援 DataSource + DataBind()
protected override void PerformSelect()
{
IEnumerable data = ResolveDataSource(); // 內部處理 DataSource
if (data != null)
{
// 建立控件階層、繫結模板...
}
}
}
public class ModernRepeater : BaseDataList // BaseDataList 2.0 已支援 DataSourceID
{
// 1. 繼承 BaseDataList 即自動獲得 DataSourceID 支援
// 2. 只需覆寫 PerformSelect(IEnumerable) 新簽名
protected override void PerformSelect(IEnumerable data)
{
// 統一處理邏輯:data 可能來自 DataSource 或 DataSourceView
if (data == null && IsBoundUsingDataSourceID)
{
data = GetDataSourceView()?.ExecuteSelect(SelectArguments);
}
if (data != null)
{
// 原有模板建立、資料繫結邏輯保持不變
CreateControlHierarchy(data);
ChildControlsCreated = true;
}
}
// 3. 確保 OnDataPropertyChanged 觸發 RequiresDataBinding
protected override void OnDataPropertyChanged()
{
base.OnDataPropertyChanged();
RequiresDataBinding = true;
}
}
DataSource 與 DataSourceID 不可同時使用,否則拋出異常DataSourceID 後不需呼叫 DataBind(),框架自動在 PreRender 執行SelectParameters 等) 需正確實作 IStateManager[IDReferenceProperty(typeof(DataSourceControl))] 讓 VS 屬性視窗提供下拉選單透過實作 IDataBoundControl 核心成員、整合生命週期 (OnPreRender 自動繫結)、統一 PerformSelect 入口,舊版資料綁定控件可平滑升級支援 DataSourceID 宣告式繫結。此遷移策略保留原有模板與繫結邏輯,僅擴展資料來源解析能力,是低風險、高回報的現代化改造路徑。