前幾篇介紹了基本資料源控件的骨架實作。本篇將深入實作 SqlDataSource 風格的進階功能,包含資料操作事件、自動重新綁定機制、CRUD 權限控制,構建生產級可用的資料源控件核心。
ASP.NET 內建資料源控件 (SqlDataSource, ObjectDataSource, LinqDataSource) 都提供豐富的前後事件,讓開發者在資料操作前後注入自訂邏輯。標準事件模式如下:
| 操作 | 前置事件 | 後置事件 | 事件參數類別 |
|---|---|---|---|
| Select | Selecting | Selected | SqlDataSourceSelectingEventArgs / SqlDataSourceStatusEventArgs |
| Insert | Inserting | Inserted | SqlDataSourceCommandEventArgs / SqlDataSourceStatusEventArgs |
| Update | Updating | Updated | SqlDataSourceCommandEventArgs / SqlDataSourceStatusEventArgs |
| Delete | Deleting | Deleted | SqlDataSourceCommandEventArgs / SqlDataSourceStatusEventArgs |
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
{
// 1. 建立命令物件 (假設 _command 為 DbCommand)
DbCommand command = CreateCommand();
// 2. 觸發 Selecting 事件 - 允許取消或修改命令
SqlDataSourceSelectingEventArgs selectArgs = new SqlDataSourceSelectingEventArgs(command, arguments);
OnSelecting(selectArgs);
if (selectArgs.Cancel)
return null; // 使用者取消操作
// 3. 執行實際查詢
IEnumerable result = ExecuteQuery(command);
// 4. 觸發 Selected 事件 - 提供執行狀態資訊
SqlDataSourceStatusEventArgs statusArgs = new SqlDataSourceStatusEventArgs(command,
(result as ICollection)?.Count ?? -1, null);
OnSelected(statusArgs);
return result;
}
當資料發生變更 (Insert/Update/Delete) 時,資料源控件必須通知繫結的資料綁定控件 (GridView、FormView 等) 重新查詢資料。這透過 DataSourceView.OnDataSourceViewChanged 方法實現:
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
{
DbCommand command = CreateDeleteCommand(keys, oldValues);
// 1. 觸發 Deleting 事件
SqlDataSourceCommandEventArgs deleteArgs = new SqlDataSourceCommandEventArgs(command);
OnDeleting(deleteArgs);
if (deleteArgs.Cancel) return 0;
int affectedRows = 0;
Exception exception = null;
try
{
// 2. 執行刪除
affectedRows = command.ExecuteNonQuery();
}
catch (Exception ex)
{
exception = ex;
throw; // 保持原始堆疊追蹤
}
finally
{
// 3. 關鍵:通知視圖已變更,觸發自動重新綁定
OnDataSourceViewChanged(EventArgs.Empty);
// 4. 觸發 Deleted 事件
SqlDataSourceStatusEventArgs statusArgs = new SqlDataSourceStatusEventArgs(command, affectedRows, exception);
OnDeleted(statusArgs);
}
return affectedRows;
}
內建資料源控件提供 CanDelete、CanInsert、CanUpdate、CanPage、CanSort 等屬性,讓開發者聲明式控制功能啟用:
// 類別層級定義
[DefaultValue(true)]
public virtual bool CanDelete { get; set; } = true;
[DefaultValue(true)]
public virtual bool CanInsert { get; set; } = true;
[DefaultValue(true)]
public virtual bool CanUpdate { get; set; } = true;
// 在各 Execute* 方法開頭檢查
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
{
if (!CanDelete)
throw new NotSupportedException("此資料源控件不支援刪除操作。請設定 CanDelete='true' 啟用。");
// ... 後續邏輯
}
protected override int ExecuteInsert(IDictionary values)
{
if (!CanInsert)
throw new NotSupportedException("此資料源控件不支援新增操作。");
// ... 後續邏輯
}
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
{
if (!CanUpdate)
throw new NotSupportedException("此資料源控件不支援更新操作。");
// ... 後續邏輯
}
支援 ConflictDetection 屬性 (OverwriteChanges | CompareAllValues):
public enum ConflictDetection
{
OverwriteChanges, // 直接覆蓋 (預設)
CompareAllValues // 比較所有舊值,偵測並發衝突
}
[DefaultValue(ConflictDetection.OverwriteChanges)]
public virtual ConflictDetection ConflictDetection { get; set; }
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
{
if (ConflictDetection == ConflictDetection.CompareAllValues)
{
// 建立 WHERE 條件包含所有舊值
// 例如:UPDATE Table SET Name=@Name WHERE Id=@Id AND OldName=@OldName AND OldEmail=@OldEmail
// 若 affectedRows == 0 則拋出 DBConcurrencyException
}
// ...
}
private DbCommand CreateSelectCommand()
{
DbCommand cmd = _factory.CreateCommand();
cmd.CommandText = SelectCommand;
cmd.CommandType = CommandType.Text; // 或 StoredProcedure
// 自動從 SelectParameters 集合建立參數
foreach (Parameter param in SelectParameters)
{
DbParameter dbParam = _factory.CreateParameter();
dbParam.ParameterName = param.Name;
dbParam.Value = param.GetValue(HttpContext.Current, _control);
dbParam.DbType = param.DbType;
dbParam.Direction = ParameterDirection.Input;
cmd.Parameters.Add(dbParam);
}
return cmd;
}
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object parametersState = ((IStateManager)SelectParameters).SaveViewState();
// ... 儲存其他參數集合
return new Pair(baseState, parametersState);
}
protected override void LoadViewState(object savedState)
{
if (savedState == null) return;
Pair state = (Pair)savedState;
base.LoadViewState(state.First);
((IStateManager)SelectParameters).LoadViewState(state.Second);
// ... 載入其他參數集合
}
[Designer(typeof(CustomDataSourceDesigner))]
[ToolboxBitmap(typeof(SqlDataSource))]
public class CustomDataSource : DataSourceControl
{
// ...
}
// 設計器提供智慧標籤:設定連線字串、配置查詢、重新整理結構描述
public class CustomDataSourceDesigner : DataSourceDesigner
{
public override DesignerActionListCollection ActionLists
{
get
{
DesignerActionListCollection lists = new DesignerActionListCollection();
lists.Add(new CustomDataSourceActionList(this));
return lists;
}
}
}
本篇完整實作了企業級資料源控件的核心功能:
OnDataSourceViewChanged 實現資料變更後的 UI 同步CanDelete/CanInsert/CanUpdate 屬性ConflictDetection這些功能構成了專業資料源控件的骨幹,掌握這些核心機制,即可開發出媲美內建 SqlDataSource 的客製化資料源控件。