职业IT人-IT人生活圈

 找回密码
 成为会员
搜索
查看: 1075|回复: 0

[转帖]《解剖PetShop》系列之六(2)

[复制链接]
蓝色梦幻 发表于 2008-10-28 08:48 | 显示全部楼层 |阅读模式
6.3  ASP.NET控件

ASP.NET控件是View对象最重要的组成部分,它充分利用了面向对象的设计思想,通过封装与继承构建一个个控件对象,使得用户在开发Web页面时,能够重用这些控件,甚至自定义自己的控件。在第8章中,我已经介绍了.NET Framework中控件的设计思想,通过引入一种“复合方式”的Composite模式实现了控件树。在ASP.NET控件中,System.Web.UI.Control就是这棵控件树的根,它定义了所有ASP.NET控件共有的属性、方法和事件,并负责管理和控制控件的整个执行生命周期。

Control基类并没有包含UI的特定功能,如果需要提供与UI相关的方法属性,就需要从System.Web.UI.WebControls.WebControl类派生。该类实际上也是Control类的子类,但它附加了诸如ForeColor、BackColor、Font等属性。

除此之外,还有一个重要的类是System.Web.UI.UserControl,即用户控件类,它同样是Control类的子类。我们可以自定义一些用户控件派生自UserControl,在Visual Studio的Design环境下,我们可以通过拖动控件的方式将多种类型的控件组合成一个自定义用户控件,也可以在codebehind方式下,为自定义用户控件类添加新的属性和方法。

整个ASP.NET控件类的层次结构如图6-2所示:



图6-2 ASP.NET控件类的层次结构

ASP.NET控件的执行生命周期下所示:(全部三行话表现形式)
三行话,按这三个方面理解:
第一句::阶段
第二句:控件需要执行的操作
第三句:要重写的方法或事件

1)初始化
初始化在传入 Web 请求生命周期内所需的设置。
Init 事件(OnInit 方法)

2)加载视图状态
在此阶段结束时,就会自动填充控件的 ViewState 属性,控件可以重写 LoadViewState 方法的默认实现,以自定义状态还原。
LoadViewState 方法

3)处理回发数据
处理传入窗体数据,并相应地更新属性。
注意:只有处理回发数据的控件参与此阶段。
LoadPostData 方法(如果已实现 IPostBackDataHandler)

4)加载
执行所有请求共有的操作,如设置数据库查询。此时,树中的服务器控件已创建并初始化、状态已还原并且窗体控件反映了客户端的数据。
Load 事件(OnLoad 方法)

5)发送回发更改通知
引发更改事件以响应当前和以前回发之间的状态更改。
注意:只有引发回发更改事件的控件参与此阶段。
RaisePostDataChangedEvent 方法(如果已实现 IPostBackDataHandler)

6)处理回发事件
处理引起回发的客户端事件,并在服务器上引发相应的事件。
注意:只有处理回发事件的控件参与此阶段。
RaisePostBackEvent 方法(如果已实现 IPostBackEventHandler)

7)预呈现
在呈现输出之前执行任何更新。可以保存在预呈现阶段对控件状态所做的更改,而在呈现阶段所对的更改则会丢失。
PreRender 事件(OnPreRender 方法)

9)保存状态
在此阶段后,自动将控件的 ViewState 属性保持到字符串对象中。此字符串对象被发送到客户端并作为隐藏变量发送回来。为了提高效率,控件可以重写 SaveViewState 方法以修改 ViewState 属性。
SaveViewState 方法

10)呈现
生成呈现给客户端的输出。
Render 方法

11)处置
执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库链接。
Dispose 方法

12)卸载
执行销毁控件前的所有最终清理操作。控件作者通常在 Dispose 中执行清除,而不处理此事件。
UnLoad 事件(On UnLoad 方法)

在这里,控件设计利用了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 = ”


“;
    protected const string HTML2 = “
“;
    protected const string HTML3 = “ “;
    protected const string HTML4 = “


“;
    private static readonly Regex RX = new Regex(@”^&page=\\d+”,
RegexOptions.Compiled);
    private const string LINK_PREV = “< Previous“;
    private const string LINK_MORE = “More >“;
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 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.”;
        }
    }

图6-2 ASP.NET控件类的层次结构

图6-2 ASP.NET控件类的层次结构
您需要登录后才可以回帖 登录 | 成为会员

本版积分规则

QQ|手机版|小黑屋|网站帮助|职业IT人-IT人生活圈 ( 粤ICP备12053935号-1 )|网站地图
本站文章版权归原发布者及原出处所有。内容为作者个人观点,并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是信息平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽造成漏登,请及时联系我们,我们将根据著作权人的要求立即更正或者删除有关内容。

GMT+8, 2024-4-29 04:45 , Processed in 0.127623 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表