|
表6-1 ASP.NET控件的执行生命周期
在这里,控件设计利用了Template Method模式,Control基类提供了大部分protected虚方法,留待其子类改写其方法。以PetShop 4.0为例,就定义了两个ASP.NET控件,它们都属于System.Web.UI.WebControls.WebControl的子类。其中,CustomList控件派生自System.Web.UI.WebControls.DataList,CustomGrid控件则派生自System.Web.UI.WebControls.Repeater。
由于这两个控件都改变了其父类控件的呈现方式,故而,我们可以通过重写父类的Render虚方法,完成控件的自定义。例如CustomGrid控件:
public class CustomGrid : Repeater…
//Static constants
protected const string HTML1 = "<table cellpadding=0
cellspacing=0><tr><td colspan=2>";
protected const string HTML2 = "</td></tr><tr><td class=paging align=left>";
protected const string HTML3 = "</td><td align=right class=paging>";
protected const string HTML4 = "</td></tr></table>";
private static readonly Regex RX = new Regex(@"^&page=\d+",
RegexOptions.Compiled);
private const string LINK_PREV = "<a href=?page={0}>< Previous</a>";
private const string LINK_MORE = "<a href=?page={0}>More ></a>";
private const string KEY_PAGE = "page";
private const string COMMA = "?";
private const string AMP = "&";
override protected void Render(HtmlTextWriter writer) {
//Check there is some data attached
if (ItemCount == 0) {
writer.Write(emptyText);
return;
}
//Mask the query
string query = Context.Request.Url.Query.Replace(COMMA, AMP);
query = RX.Replace(query, string.Empty);
// Write out the first part of the control, the table header
writer.Write(HTML1);
// Call the inherited method
base.Render(writer);
// Write out a table row closure
writer.Write(HTML2);
//Determin whether next and previous buttons are required
//Previous button?
if (currentPageIndex > 0)
writer.Write(string.Format(LINK_PREV, (currentPageIndex - 1) + query));
//Close the table data tag
writer.Write(HTML3);
//Next button?
if (currentPageIndex < PageCount)
writer.Write(string.Format(LINK_MORE, (currentPageIndex + 1) + query));
//Close the table
writer.Write(HTML4);
}
由于CustomGrid继承自Repeater控件,因而它同时还继承了Repeater的DataSource属性,这是一个虚属性,它默认的set访问器属性如下:
public virtual object DataSource
{
get {… }
set
{
if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
{
throw new ArgumentException(SR.GetString("Invalid_DataSource_Type", new object[] { this.ID }));
}
this.dataSource = value;
this.OnDataPropertyChanged();
}
}
对于CustomGrid而言,DataSource属性有着不同的设置行为,因而在定义CustomGrid控件的时候,需要改写DataSource虚属性,如下所示:
private IList dataSource;
private int itemCount;
override public object DataSource {
set {
//This try catch block is to avoid issues with the VS.NET designer
//The designer will try and bind a datasource which does not derive from ILIST
try {
dataSource = (IList)value;
ItemCount = dataSource.Count;
}
catch {
dataSource = null;
ItemCount = 0;
}
}
}
当设置的value对象值不为IList类型时,set访问器就将捕获异常,然后将dataSource字段设置为null。
由于我们改写了DataSource属性,因而改写Repeater类的OnDataBinding()方法也就势在必行。此外,CustomGrid还提供了分页的功能,我们也需要实现分页的相关操作。与DataSource属性不同,Repeater类的OnDataBinding()方法实际上是继承和改写了Control基类的OnDataBinding()虚方法,而我们又在此基础上改写了Repeater类的OnDataBinding()方法:
override protected void OnDataBinding(EventArgs e) {
//Work out which items we want to render to the page
int start = CurrentPageIndex * pageSize;
int size = Math.Min(pageSize, ItemCount - start);
IList page = new ArrayList();
//Add the relevant items from the datasource
for (int i = 0; i < size; i++)
page.Add(dataSource[start + i]);
//set the base objects datasource
base.DataSource = page;
base.OnDataBinding(e);
}
此外,CustomGrid控件类还增加了许多属于自己的属性和方法,例如PageSize、PageCount属性以及SetPage()方法等。正是因为ASP.NET控件引入了Composite模式与Template Method模式,当我们在自定义控件时,就可以通过继承与改写的方式来完成控件的设计。自定义ASP.NET控件一方面可以根据系统的需求实现特定的功能,也能够最大限度地实现对象的重用,既可以减少编码量,同时也有利于未来对程序的扩展与修改。
在PetShop 4.0中,除了自定义了上述WebControl控件的子控件外,最主要的还是利用了用户控件。在Controls文件夹下,一共定义了11个用户控件,内容涵盖客户地址信息、信用卡信息、购物车信息、期望列表(Wish List)信息以及导航信息、搜索结果信息等。它们相当于是一些组合控件,除了包含了子控件的方法和属性外,也定义了一些必要的UI实现逻辑。以ShoppingCartControl用户控件为例,它会在该控件被呈现(Render)之前,做一些数据准备工作,获取购物车数据,并作为数据源绑定到其下的Repeater控件:
public partial class ShoppingCartControl : System.Web.UI.UserControl
protected void Page_PreRender(object sender, EventArgs e) {
if (!IsPostBack) {
BindCart();
}
}
private void BindCart() {
ICollection<CartItemInfo> cart = Profile.ShoppingCart.CartItems;
if (cart.Count > 0) {
repShoppingCart.DataSource = cart;
repShoppingCart.DataBind();
PrintTotal();
plhTotal.Visible = true;
}
else {
repShoppingCart.Visible = false;
plhTotal.Visible = false;
lblMsg.Text = "Your cart is empty.";
}
}
在ShoppingCart页面下,我们可以加入该用户控件,如下所示:
<PetShopControl:shoppingcartcontrol id="ShoppingCartControl1" runat="server"></PetShopControl:shoppingcartcontrol>
由于ShoppingCartControl用户控件已经实现了用于呈现购物车数据的逻辑,那么在ShoppingCart.aspx.cs中,就可以不用负责这些逻辑,在充分完成对象重用的过程中,同时又达到了职责分离的目的。用户控件的设计者与页面设计者可以互不干扰,分头完成自己的设计。特别是对于页面设计者而言,他可以是单一的UI设计人员角色,仅需要关注用户界面是否美观与友好,对于表示层中对领域对象的调用与操作就可以不必理会,整个页面的代码也显得结构清晰、逻辑清楚,无疑也“干净”了不少。
(责任编辑:admin) |