• 【转】Asp.net控件开发学习笔记整理篇


    最近一直在做MVC项目,对于WEBFORM 好像快忘记了。周末无聊,顺带看看他人的笔记。再次温习下。

    复习大纲:

    导航、页面生命周期及其它导论

    一、服务器控件生命周期

    二、控件开发基础

    三、Asp.net服务端状态管理

    四、Asp.net客户端状态管理

    五、数据回传

    六、WebControl基类

    七、服务器控件事件

    事件和委托之间的暧昧关系往往是大多Web Developer在学习.net中的一个里程碑,当明白事件和委托的关系后,.net水平往往就上了一个新的台阶。

    下面说到服务器控件的事件模型.

    在任何一个服务器编程开发框架中,事件都是解耦功能和具体实现的一剂良方,Asp.net当然也不例外。比如说吧,页面上的button的click事件表示它的功能,而具体的实现将会被分离交给Developer来进行具体实现。

    传统的编程模型和基于事件的编程模型可以用下图进行简约概括:

       
     

    我们可以看出事件极大的简化了编程工作,客户端程序只需要注册到事件并且和事件的签名保持一致(即参数个数和类型相同)即可。在事件发生后,客户端程序会被通知并执行相应实现(.net framework的事件正是观察者模式的最好例子:-)

    Asp.net通过ViewStateHttp Post协议巧妙的实现了让开发人员感觉貌似控件能像WinForm程序中那样记住自己的状态。这使Asp.net可以在不使用客户端javascript的情况下,而实现数据回传。

        

        上面的图例展示出TextBox通过暴露相应的事件来通知被注册的函数.还记得前面所说的IPostDataHandler接口嘛,大多数服务器控件的事件都是通过ViewState来将数据以Http Post协议传回服务器,服务器根据回传数据的不同来引发相应事件。因此我们可以看出Button控件生成的<input type=”submit” />是有往服务器提交的功能的,而其他控件比如DropDownList或者是Checkbox是没有像服务器提交的功能。因此引发服务器事件便无从谈起。Asp.net通过在客户端设置javascript事件来引发向服务器的Http Post回传。而这一切仅仅需要将AutoPostBack属性设置为true.

      .net FrameWork 事件模型

       .net framework提供了基于委托的使不同类之间进行异步交互的机制。下面先简单说一下事件的核心-------委托

    Delegate

    委托在一定程度上有点像接口,是在发布者和订阅者之间的一个协议。接口是制订类的数据成员以及成员函数的签名。而委托,是制订单个函数的签名.

    创建一个委托的实例是通过创建一个和委托相匹配的函数。

    MSDN里把委托比喻成类型安全的函数指针。但是委托不仅限于此,因为.net framework大大的扩展了这个“类型安全的指针”,在CLR via C#这本书里说委托的实质上是一个类。因此委托可以按照次序依次调用多个匹配的方法,无论是静态方法还是实例方法。

    委托有两个部分,委托的声明和委托实例的声明。

    委托的声明代码会像:

    public delegate void foo(string A);

    而委托实例的声明会像

    foo f=new foo(MethodName)

    下面通过一个简单的Demo说明一下:

    Demo 委托

        先写一个简单的类:  

    public class DelegateDemo
    
        {
    
            public delegate void DeleDemo(string a);
    
            public static void FunctionA(string a)
    
            {
    
                HttpContext.Current.Response.Write("静态方法,传入的参数是:" + a + "<br />");
    
            }
    
            public void FunctionB(string a)
    
            {
    
                HttpContext.Current.Response.Write("实例方法,传入的参数是:" + a + "<br />");
    
            }
    
    }
    

    下面是客户端代码:        

    DelegateDemo dd = new DelegateDemo();
    
    DelegateDemo.DeleDemo d = new DelegateDemo.DeleDemo(dd.FunctionB);
    
    d += DelegateDemo.FunctionA;
    
    d("参数");

    最后的输出结果为:

    实例方法,传入的参数是:参数
    静态方法,传入的参数是:参数

        通过上面小Demo可以发现委托不仅仅是“类型安全的指针”,并且委托是按照次序调用实例方法和静态方法.

    事件

       C#里有专门用于声明事件的event关键字,一个典型的事件生命会像:

    public event EventHandler click; 

    事件关键字后面是委托,再后面是事件名称,命名事件的名称最好是动词,表名某些事情发生了。比如click,Init,Load,TextChanged这样。由此可以看出事件其实就是特殊的委托。因为所有的事件都是继承于System.EventHandler.

    EventHandler 委托

    所有asp.net内置控件事件处理函数的签名都和EventHandler或者继承于它的子类保持一致。它的原型是:

    [SerializableAttribute]
    
    [ComVisibleAttribute(true)]
    
    public delegate void EventHandler(Object sender,EventArgs e)

    第一个参数表示引发事件的对象,第二个参数表示引发事件后所要传给处理程序的参数。

    在一般情况下,开发人员最好是按照这种签名格式来声明函数事件的委托.

    在控件内部声明事件后,你必须在需要的情况下引发事件,直接引发事件是非常不好的做法。而在asp.net预定义的控件中都使用了如下方法:

     声明一个virtual protected void的方法,命名方式为On+事件名称.下面是一个例子:

          

    protected virtual void OnClick(EventArgs e)
    
            {
    
                if (Click != null)
    
                    Click(this, e);
    
           }
    

    这个方法首先做的是先检查客户端方法是否注册,如果有客户端方法进行了注册,则引发事件。

    EventCollection

    如果在单个控件中有多个事件,那么使用System.ComponentModel.EventHandlerList对事件进行保存将会在内存占用上有不错的提高。EventHandlerList对一个类内发布多个事件提供了一个列表容器。下面是多个事件和使用EventHandlerList的对比示意:

      

    第一步是实例化一个EventHandlerList的实例:

    protected EventHandlerList eventList = new EventHandlerList();

    第二步是声明一个容器用于保存事件的key

    private static readonly object ClickEvent = new object();

    最后一步是像往常一样声明一个事件,但有所不同的是就像属性的get和set程序块一样,对于事件C#提供了add和remove关键字:

    public event EventHandler Click
    
            {
    
                add
    
                {
    
                    Events.AddHandler(ClickEvent, value);
    
                }
    
                remove
    
                {
    
                    Events.RemoveHandler(ClickEvent, value);
    
                }
    
          }
    

    而在这时的事件调用方法就会像下面代码:

    protected virtual void OnClick(EventArgs e)
    
            {
    
                EventHandler clickEventDelegate = (EventHandler)Events[ClickEvent];
    
                if (clickEventDelegate != null)
    
                 {
    
                    clickEventDelegate(this, e);
    
                }
    
         }
    

    上面代码首先从事件列表中通过索引器以第一步中保存事件的key为参数提取出事件并检查客户端是否注册到此事件,如果是,则激发事件。

     Command事件和事件冒泡

        Command事件是System.Web.UI.WebControls命名空间里的强大模式。这个最好的例子是GridView


        

     在GridView的Row里嵌套的button点击会触发Command事件,后台可以根据CommandArgument的不同来决定是执行edit操作还是delete操作等。而事件冒泡有些像javascript里的事件冒泡,但有所不同的是这里的事件冒泡到能够处理这个事件的地方停止,比如上图中command事件会冒泡到DataGrid里的ItemCommand里停止,因为ItemCommand事件可以对command事件进行处理.

     在定义Command事件时会和前面大同小异,不同之处在于首先需要一个继承与System.EventArgs的CommandEventArgs类来进行参数传递,代码如下

    public class CommandEventArgs : EventArgs
            {
                public CommandEventArgs(string _commandName, string _commandArgument)
                {
                    CommandName = _commandName;
                    CommandArgument = _commandArgument;
                }
    
                private string commandname;
                private string commandArgument;
                public virtual string CommandName
                {
                    get
                    {
                        return commandname;
                    }
                    set
                    {
                        commandname = value;
                    }
                }
                public virtual string CommandArgument
                {
                    get
                    {
                        return commandArgument;
                    }
                    set
                    {
                        commandArgument = value;
                    }
                }
            }
    

      然后在需要定义的控件里定义这两个属性,代码如下:

             

    public virtual string CommandName
            {
                get
                {
                    object name = ViewState["CommandName"];
                    if (name == null)
                        return string.Empty;
                    else
                        return (string)name;
                }
                set
                {
                    ViewState["CommandName"] = value;
                }
            }
            public virtual string CommandArgument
            {
                get
                {
                    object arg = ViewState["CommandArgument"];
                    if (arg == null)
                        return string.Empty;
                    else
                        return (string)arg;
                }
                set
                {
                    ViewState["CommandArgument"] = value;
                }
            }
    

    然后重复前面的步骤,在控件内部定义命令事件:

    private static readonly object CommandKey = new object();
    
            public event CommandEventHandler Command
            {
                add
                {
                    Events.AddHandler(CommandKey, value);
                }
                remove
                {
                    Events.RemoveHandler(CommandKey, value);
                }
            }
    

    最后一步和前面说的引发事件的OnXXX的实现都略有不同,这里在控件内部实现的代码如下:       

    protected virtual void OnCommand(CommandEventArgs ce)
            {
                CommandEventHandler commandEventDelegate = (CommandEventHandler)Events[CommandKey];
                if (commandEventDelegate != null)
                {
                    commandEventDelegate(this, ce);
                }
                RaiseBubbleEvent(this, ce);
            }
    

    注意最后一句,RaiseBubbleEvent方法.这个方法可以将控件的事件传递到它的父容器上。

     到这里很多人都会好奇,那CommandName和CommandArgument两个参数是如何传入到CommandEventArgs里去的呢?

     其实是在引发事件时传入的,代码如下:

      OnCommand(new CommandEventArgs(CommandName, CommandArgument));

    DEMO 带Command事件的Button

        其实这个Demo就是把上面的代码全部拼装起来,代码可能会有点长,代码如下:    

    namespace DemoButton
    {
        using System;
    
        [ToolboxData("<{0}:superbutton runat=server></{0}:superbutton>")]
        public class ButtonDemo : Control, IPostBackEventHandler
        {
            public delegate void CommandEventHandler(object sender, CommandEventArgs e);
            public virtual string Text
            {
                get
                {
                    object text = ViewState["Text"];
                    if (text == null)
                        return string.Empty;
                    else
                        return (string)text;
                }
                set
                {
                    ViewState["Text"] = value;
                }
            }
            private static readonly object ClickKey = new object();
            public event EventHandler Click
            {
                add
                {
                    Events.AddHandler(ClickKey, value);
                }
                remove
                {
                    Events.RemoveHandler(ClickKey, value);
                }
            }
            protected virtual void OnClick(EventArgs e)
            {
                EventHandler clickEventDelegate = (EventHandler)Events[ClickKey];
                if (clickEventDelegate != null)
                {
                    clickEventDelegate(this, e);
                }
            }
            private static readonly object CommandKey = new object();
            public event CommandEventHandler Command
            {
                add
                {
                    Events.AddHandler(CommandKey, value);
                }
                remove
                {
                    Events.RemoveHandler(CommandKey, value);
                }
            }
            public virtual string CommandName
            {
                get
                {
                    object name = ViewState["CommandName"];
                    if (name == null)
                        return string.Empty;
                    else
                        return (string)name;
                }
                set
                {
                    ViewState["CommandName"] = value;
                }
            }
            public virtual string CommandArgument
            {
                get
                {
                    object arg = ViewState["CommandArgument"];
                    if (arg == null)
                        return string.Empty;
                    else
                        return (string)arg;
                }
                set
                {
                    ViewState["CommandArgument"] = value;
                }
            }
            protected virtual void OnCommand(CommandEventArgs ce)
            {
                CommandEventHandler commandEventDelegate = (CommandEventHandler)Events[CommandKey];
                if (commandEventDelegate != null)
                {
                    commandEventDelegate(this, ce);
                }
                RaiseBubbleEvent(this, ce);
            }
    
            public void RaisePostBackEvent(string argument)
            {
                OnCommand(new CommandEventArgs(CommandName, CommandArgument));
                //OnClick(EventArgs.Empty);
            }
    
            protected override void Render(HtmlTextWriter writer)
            {
                base.Render(writer);
                Page.VerifyRenderingInServerForm(this);
                writer.Write("<INPUT type=""submit""");
                writer.Write(" name=""" + this.UniqueID + """");
                writer.Write(" id=""" + this.UniqueID + """");
                writer.Write(" value=""" + Text + """");
                writer.Write(" />");
            }
        }
    
        public class CommandEventArgs : EventArgs
        {
            public CommandEventArgs(string _commandName, string _commandArgument)
            {
                CommandName = _commandName;
                CommandArgument = _commandArgument;
            }
            private string commandname;
            private string commandArgument;
            public virtual string CommandName
            {
                get
                {
                    return commandname;
                }
                set
                {
                    commandname = value;
                }
            }
            public virtual string CommandArgument
            {
                get
                {
                    return commandArgument;
                }
                set
                {
                    commandArgument = value;
                }
            }
        }
    }
    

      

    前台代码:

    首先注册页面控件:

     <%@ Register Namespace="DemoButton" TagPrefix="cc" %>

    前台代码:

       <cc:ButtonDemo runat="server" Text="第一个按钮" ID="bt1" CommandName="bt1"

            CommandArgument="第一个button的参数" oncommand="bt1_Command" ></cc:ButtonDemo>

          

     <cc:ButtonDemo runat="server" Text="第二个按钮" ID="bt2" CommandName="bt1"

            CommandArgument="第二个button的参数" oncommand="bt1_Command" ></cc:ButtonDemo>

    事件处理程序:

        protected void bt1_Command(object sender, DemoButton.CommandEventArgs e)

        {

            if (e.CommandName == "bt1")

            {

                Response.Write("第一个button被点击了,参数是"+e.CommandArgument);

            }

            else if (e.CommandArgument == "bt12")

            {

                Response.Write("第二个button被点击了,参数是" + e.CommandArgument);

            }

    }

    Demo的结果很简单,就不演示了:-)

  • 相关阅读:
    支付功能测试用例设计要点
    接口测试用例设计实践总结
    如何高效学习
    性能测试基础总结和自己的理解
    linux安装AWStats业务数据分析工具
    python3登陆接口测试
    Python2和Python3中urllib库中urlencode的使用注意事项
    运用BT在centos下搭建一个博客论坛
    python环境搭建-requests的简单安装(适合新手)
    web 资源好文
  • 原文地址:https://www.cnblogs.com/taoqianbao/p/3486267.html
Copyright © 2020-2023  润新知