ASP.NET 1.x 時代,資料綁定意味著:grid.DataSource = GetData(); grid.DataBind();。ASP.NET 2.0 引入資料源控件 (SqlDataSource, ObjectDataSource) 後,開發模式徹底改變:
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="CompanyName" HeaderText="公司名稱" SortExpression="CompanyName" />
<asp:BoundField DataField="ContactName" HeaderText="聯絡人" SortExpression="ContactName" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
SelectCommand="SELECT CompanyName, ContactName FROM Customers">
</asp:SqlDataSource>
零程式碼實現資料查詢、分頁、排序、CRUD。但這背後是什麼機制?如何開發自己的新版資料綁定控件?
public abstract class BaseDataBoundControl : Control, IDataBoundControl
{
// 核心屬性
public string DataSourceID { get; set; }
public virtual IEnumerable DataSource { get; set; }
public string DataMember { get; set; }
protected bool RequiresDataBinding { get; set; }
protected DataSourceSelectArguments SelectArguments { get; }
// 核心方法
protected abstract void PerformDataBinding(IEnumerable data);
protected virtual void OnDataPropertyChanged() { RequiresDataBinding = true; }
protected virtual DataSourceView GetDataSourceView() { /* 解析 DataSourceID */ }
protected virtual IEnumerable GetData() { /* 從 DataSource 或 DataSourceView 取得 */ }
// 生命週期
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (RequiresDataBinding) EnsureDataBound();
}
}
public abstract class DataBoundControl : BaseDataBoundControl
{
// 擴展功能:分頁、排序、樣式、模板
public virtual int PageSize { get; set; }
public virtual bool AllowPaging { get; set; }
public virtual bool AllowSorting { get; set; }
public virtual string SortExpression { get; }
public virtual SortDirection SortDirection { get; }
// 樣式屬性
public TableItemStyle HeaderStyle { get; }
public TableItemStyle FooterStyle { get; }
public TableItemStyle RowStyle { get; }
public TableItemStyle AlternatingRowStyle { get; }
public TableItemStyle EditRowStyle { get; }
public TableItemStyle SelectedRowStyle { get; }
public TableItemStyle PagerStyle { get; }
// 模板 (適用於 FormView, DetailsView 等)
public virtual ITemplate ItemTemplate { get; set; }
public virtual ITemplate EditItemTemplate { get; set; }
// ...
}
DataSourceID 尋找 IDataSource 實作GetView("") 取得 DataSourceViewCanPage、CanSort、CanRetrieveTotalRowCountDataSourceSelectArguments (包含分頁、排序資訊)ExecuteSelect(arguments) 取得 IEnumerablePerformDataBinding(data) 建立 UI 階層DataSourceView.OnDataSourceViewChanged 觸發 RequiresDataBinding = true
public class DataSourceSelectArguments
{
public int StartRowIndex { get; set; } // 分頁起始索引
public int MaximumRows { get; set; } // 每頁筆數
public string SortExpression { get; set; } // 排序運算式
public bool RetrieveTotalRowCount { get; set; } // 是否需要總筆數
// 能力檢查:若視圖不支援分頁但要求分頁,拋出異常
public void RaiseUnsupportedCapabilitiesError(DataSourceView view) { ... }
}
[ToolboxData("<{0}:SimpleList runat=server>{0}:SimpleList>")]
public class SimpleList : DataBoundControl
{
// 1. 定義項目樣式
[PersistenceMode(PersistenceMode.InnerProperty)]
public Style ItemStyle { get; } = new Style();
[PersistenceMode(PersistenceMode.InnerProperty)]
public Style AlternatingItemStyle { get; } = new Style();
// 2. 核心繫結邏輯
protected override void PerformDataBinding(IEnumerable data)
{
base.PerformDataBinding(data);
Controls.Clear();
if (data == null) return;
int index = 0;
foreach (object item in data)
{
// 建立項目容器
Control itemContainer = new Control();
itemContainer.ID = $"item{index}";
// 套用樣式
Style style = (index % 2 == 0) ? ItemStyle : AlternatingItemStyle;
style.ApplyTo(itemContainer);
// 繫結資料 (簡化:直接 ToString)
Literal lit = new Literal { Text = item?.ToString() ?? "" };
itemContainer.Controls.Add(lit);
Controls.Add(itemContainer);
index++;
}
ChildControlsCreated = true;
}
// 3. 渲染輸出
protected override void RenderContents(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
foreach (Control item in Controls)
{
writer.RenderBeginTag(HtmlTextWriterTag.Li);
item.RenderControl(writer);
writer.RenderEndTag(); // li
}
writer.RenderEndTag(); // ul
}
}
需支援 ItemTemplate、AlternatingItemTemplate 等模板時,繼承 BaseDataBoundControl 並實作 INamingContainer:
public class TemplatedList : BaseDataBoundControl, INamingContainer
{
// 模板屬性
[TemplateContainer(typeof(TemplatedListItem))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual ITemplate ItemTemplate { get; set; }
[TemplateContainer(typeof(TemplatedListItem))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual ITemplate AlternatingItemTemplate { get; set; }
// 項目容器類別
public class TemplatedListItem : Control, INamingContainer, IDataItemContainer
{
public int ItemIndex { get; set; }
public object DataItem { get; set; }
public int DataItemIndex => ItemIndex;
public int DisplayIndex => ItemIndex;
}
protected override void PerformDataBinding(IEnumerable data)
{
base.PerformDataBinding(data);
Controls.Clear();
if (data == null) return;
int index = 0;
foreach (object item in data)
{
TemplatedListItem container = new TemplatedListItem
{
ItemIndex = index,
DataItem = item
};
// 實例化對應模板
ITemplate template = (index % 2 == 0) ? ItemTemplate : AlternatingItemTemplate;
template?.InstantiateIn(container);
// 觸發 DataBinding 事件
container.DataBind();
Controls.Add(container);
index++;
}
ChildControlsCreated = true;
}
}
| 維度 | 舊架構 (BaseDataList/DataGrid) | 新架構 (BaseDataBoundControl/DataBoundControl) |
|---|---|---|
| 基類 | BaseDataList (針對列表) | BaseDataBoundControl (通用基礎) |
| 資料來源 | DataSource + DataBind() | DataSourceID (宣告式) + 自動繫結 |
| 分頁/排序 | 自行實作 | 框架內建支援 (透過 DataSourceView) |
| 模板支援 | 固定模板集合 | 彈性模板 + IDataItemContainer |
| 樣式管理 | 分散屬性 | Style 物件集中管理 |
| 設計時支援 | 有限 | 完整智慧標籤、樣式編輯器 |
DataItem、DataItemIndex,啟用 Eval/Bind 語法IStateManagerDesigner、ToolboxBitmap、智慧標籤動作清單ItemCreated、ItemDataBound、ItemCommand 等ASP.NET 2.0 重新設計資料綁定架構,核心在於關注點分離:資料綁定控件專注 UI 渲染與互動,資料源控件專注資料存取與操作。BaseDataBoundControl/DataBoundControl 提供豐富基礎設施,讓開發者專注業務邏輯而非基礎管線。掌握此架構,是開發專業級伺服器控件的關鍵門檻。