| |
打造通用ASP.NET数据分页控件 |
|
时间: 2003-09-12 来自:yesky |
 |
|
1e2b
在DataBound中,我们尝试通过Reflection API获得DataSource属性,然后返回实际数据源的一个引用。现在虽然已经获知了数据源,但分页控件还必须知道如何操作该数据源。为了让分页控件不依赖于特定的表现控件,问题复杂了很多。不过,如果让分页控件依赖于特定的数据源,那就背离了设计一个灵活的分页控件的目标。我们要通过一个接插式的体系结构来确保分页控件能够处理各种数据源,无论是.NET提供的数据源,还是自定义的数据源。
为了提供一个健壮的、可伸缩的接插式体系结构,我们将利用[GoF] Builder模式构造出一个解决方案。
 图四
IDataSourceAdapter接口定义了分页控件操作数据所需的最基本的元素,相当于“插头”。
TotalCount属性返回在处理数据之前数据源所包含元素的总数,而GetPagedData方法返回原始数据的一个子集,例如:假设数据源是一个包含20个元素的数组,分页控件将数据显示成每页10个元素,则第一页的元素子集是数组元素0-9,第二页的元素子集是数组元素10-19。DataViewAdapter提供了一个DataView类型的插头:
internal class DataViewAdapter:IDataSourceAdapter { private DataView _view;
internal DataViewAdapter(DataView view) { _view = view; } public int TotalCount { get{return (_view == null) ? 0 : _view.Table.Rows.Count;} } public object GetPagedData(int start, int end) { DataTable table = _view.Table.Clone();
for (int i = start;i<=end && i<= TotalCount;i++) { table.ImportRow(_view[i-1].Row); } return table; } } | DataViewAdapter实现了IDataSourceAdapter的GetPagedData方法,该GetPagedData克隆原始的DataTable,将原始DataTable中的数据导入到新的DataTable。该类的可见性有意地设置成internal,目的是为了向Web开发者隐藏实现细节,进而通过Builder类提供一个更简单的接口。
public abstract class AdapterBuilder { private object _source;
private void CheckForNull() { if (_source == null) throw new NullReferenceException("必须提供一个合法的数据源"); } public virtual object Source { get { CheckForNull(); return _source;} set { _source = value; CheckForNull(); } } public abstract IDataSourceAdapter Adapter{get;} } | AdapterBuilder抽象类为IdataSourceAdapter类型提供了一个更容易管理的接口,由于提高了抽象程度,我们不必再直接使用IdataSourceAdapter,同时AdapterBuilder还提供了在分页数据之前执行预处理的指令。另外,该Builder还使得实际的实现类,例如DataViewAdapter,对分页控件的用户透明:
public class DataTableAdapterBuilder:AdapterBuilder { private DataViewAdapter _adapter;
private DataViewAdapter ViewAdapter { get { if (_adapter == null) { DataTable table = (DataTable)Source; _adapter = new DataViewAdapter(table.DefaultView); } return _adapter; } } public override IDataSourceAdapter Adapter { get { return ViewAdapter; } } } public class DataViewAdapterBuilder:AdapterBuilder { private DataViewAdapter _adapter;
private DataViewAdapter ViewAdapter { get { // 延迟实例化 if (_adapter == null) { _adapter = new DataViewAdapter((DataView)Source); } return _adapter; } } public override IDataSourceAdapter Adapter { get{return ViewAdapter;} } } | DataView类型和DataTable类型的关系是如此密切,所以构造一个通用性的DataAdapter可能是有意义的,其实只要加入另一个处理DataTable的构造函数就足够了。遗憾的是,当用户需要不同的功能来处理某个DataTable时,就必须替换或继承整个类。如果我们构造一个使用同一IdataSourceAdapter的新Builder,用户在选择如何实现适配器时就拥有更多的自由。
在分页控件中,寻找适当Builder类的操作由一个类型安全的集合完成。
public class AdapterCollection:DictionaryBase { private string GetKey(Type key) { return key.FullName; } public AdapterCollection() {} publicvoid Add(Type key,AdapterBuilder value) { Dictionary.Add(GetKey(key),value); } publicbool Contains(Type key) { return Dictionary.Contains(GetKey(key)); } publicvoid Remove(Type key) { Dictionary.Remove(GetKey(key)); } public AdapterBuilder this[Type key] { get{return (AdapterBuilder)Dictionary[GetKey(key)];} set{Dictionary[GetKey(key)]=value;} } } | AdapterCollection依赖于DataSource类型,DataSource通过BoundControl_DataBound巧妙地引入。这里使用的索引键是Type.FullName方法,确保了每一种类型索引键的唯一性,同时这也把保证每一种类型只有一个Builder的责任赋予了AdapterCollection。将Builder查找加入BoundControl_DataBound方法,结果如下:
public AdapterCollection Adapters { get{return _adapters;} }
private bool HasParentControlCalledDataBinding { get{return _builder != null;} }
private void BoundControl_DataBound(object sender,System.EventArgs e) { if (HasParentControlCalledDataBinding) return; Type type = sender.GetType(); _datasource = type.GetProperty("DataSource"); if (_datasource == null) throw new NotSupportedException("分页控件要求表现控件必需包含一个DataSource。"); object data = _datasource.GetGetMethod().Invoke(sender,null); _builder = Adapters[data.GetType()]; if (_builder == null) throw new NullReferenceException("没有安装适当的适配器来处理下面的数据源类型:"+data.GetType()); _builder.Source = data;
ApplyDataSensitivityRules(); BindParent(); RaiseEvent(DataUpdate,this); } | BoundControl_DataBound方法利用HasParentControlCalledDataBinding检查是否已经创建了Builder,如果是,则不再执行寻找适当Builder的操作。Adapters表的初始化在构造函数中完成:
public Pager() { SelectedPager=new System.Web.UI.WebControls.Style(); UnselectedPager = new System.Web.UI.WebControls.Style(); _adapters = new AdapterCollection(); _adapters.Add(typeof(DataTable),new DataTableAdapterBuilder()); _adapters.Add(typeof(DataView),new DataViewAdapterBuilder()); } | 最后一个要实现的方法是BindParent,用来处理和返回数据。
private void BindParent() { _datasource.GetSetMethod().Invoke(BoundControl, new object[]{_builder.Adapter.GetPagedData(StartRow,ResultsToShow*CurrentPage)}); } | 这个方法很简单,因为数据处理实际上是由Adapter完成的。这一过程结束后,我们还要用一次Reflection API,不过这一次是设置表现控件的DataSource属性。
15e
|
|
|
|
|
|
|
|