ASP.NET组件编程step by step

来源:互联网 发布:淘宝搜索最多的关键词 编辑:程序博客网 时间:2024/04/28 15:57

转自:椰子林DotNet的天空

       基础知识

       在ASP.net中做一个复合控件不是很难了,但是如果是刚刚接触的话,可能还是需要指点一下方向,所以不妨在这里啰嗦一下,高手们莫怪。

       Javascript:当今最流行的客户端脚本语言,如果要做出特效来他最有能耐。

       CSS:级联样式表,用于设置元素的样式。

       DHTML:动态HTML,和浏览器交互会用到。

       System.Web.UI.WebControls.WebControl:在ASP.NET中自定义控件的基类,我们可能会重写他的一些方法,常用的有:

       CreateChildControls():给予控件创建内定子控件的机会,预设情况OnPreRender()方法会调用此方法要求该控件创建其子控件,别一种情况则是由FindControl()方法调用。

       OnPreRender():触发PreRender事件,在绘制控件之前发生。

       Render():绘制控件。

       AddAttributesToRender():向标签中添加HTML样式或属性。

       另外,还有一个方法:EnsureChildControls(),该方法用于确定服务器控控件是否包含子控件,如果不包含,则创建子控件,在设计阶段起作用。

    其他的慢慢去看啦,这几个是我们的例子中会用到的。

 

    最终效果

    先看看效果,这样心中才有明确的目标。

          没有任何鼠标动作                      鼠标移到控件上                       鼠标移出后还原

 
 

1、  我们先完成效果部分,也就是编写javascript脚本。我的方法是写一个HTML文件,试验脚本的正确性:

<HTML>

<HEAD>

<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">

<TITLE></TITLE>

 

<script language=javascript>

var OldColor;

function mouseover(ctrl,color)

{

        OldColor = ctrl.style.backgroundColor;

        ctrl.style.color = "#ffffff";

        ctrl.style.backgroundColor = color;

        

}

function mouseout(ctrl)

{

        ctrl.style.backgroundColor = OldColor;

        ctrl.style.color = "#000000";

}

</script>

</HEAD>

<BODY>

 

<table  border="0" style="font-size:9pt" onmouseover="mouseover(this,’#ff0044’)" onmouseout="mouseout(this)">

<tr >

        <td><span>姓名:</span></td><td><input name="_ctl2" type="text" /></td>

</tr>

</table>

 

</BODY>

</HTML>

新建一个文本文件,将这段代码复制过去,然后将文件的扩展名改为.html,用IE打开,是不是看到了想要的效果?

 

2、  前期工作准备完成了,接下来打开VS.NET IDE,新建一个项目,选择Visual C#项目(别问我要VB.net的代码,我不会),在右边列表中选择“WEB控件库”,输入项目名称:LabelTextBox。

3、  把自动生成的代码删除,只留下一个类的框架,像下面这样(命名空间自定):

using System;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.ComponentModel;

using System.Drawing;

 

namespace LzhTextBpx

{       

    public class LabelTextbox : System.Web.UI.WebControls.WebControl

{

}

}

 

4、  上面看到的效果其实是一个一行两列的表格,我们把表格的border属性设成0,所以看不出来了,其中左边单元格放标签,右边单元格放文本框,所以,我们要用代码生成这个表格。用到的类有:

a)         Table:表示表格

b)        TableRow:表示表格行

c)        TableCell:表示表格中的单元格

注意:一个表格可以有多个表格行,一个表格行中可以放置多个单元格

生成代码如下:

//定义一个表对象

            Table t = new Table();

            //添加一行

            TableRow tr = new TableRow();

            //添加两个单元格

            TableCell tc1 = new TableCell();

            TableCell tc2 = new TableCell();

            //将控件添加到Controls集中.

            tc1.Controls.Add(label);

            tc2.Controls.Add(textBox);

            tr.Controls.Add(tc1);

            tr.Controls.Add(tc2);

            t.Controls.Add(tr);

this.Controls.Add(t);   

我们还要响应表格的鼠标移入移出事件,背景色的变化就是在这里触发的,下面的代码完成此事:

//添加鼠标事件

        t.Attributes.Add("onmouseover","mouseover(this,’" + "#" + R + G + B + "’)");

t.Attributes.Add("onmouseout","mouseout(this)");

Attributes表示表格的属性集,Add()方法用于添加一个新的属性。

顺便把表格中的字体也设置一下:

//添加样式,用来控制字体

t.Style.Add("font-size","10pt");

看到上面的R、G、B三个变量了吗?这三个变量是某种颜色的十六进制的字符串表示。使用如下的方法对颜色进行分解:

//以下将颜色值转化成十六进制表示

        string R,G,B;

        R = (Convert.ToInt32(this._backgroundColor.R)).ToString("X");

        G = (Convert.ToInt32(this._backgroundColor.G)).ToString("X");

B = (Convert.ToInt32(this._backgroundColor.B)).ToString("X");

其中_backgroundColor是自定义属性。

 

这一步的全部代码如下:

private void CreateControls()//创建控件以及设置控件的相关属性

        {

            //以下将颜色值转化成十六进制表示

            string R,G,B;

            R = (Convert.ToInt32(this._backgroundColor.R)).ToString("X");

            G = (Convert.ToInt32(this._backgroundColor.G)).ToString("X");

            B = (Convert.ToInt32(this._backgroundColor.B)).ToString("X");

            //定义一个表对象

            Table t = new Table();

 

            //添加鼠标事件

            t.Attributes.Add("onmouseover","ltmouseover(this,’" + "#" + R + G + B + "’)");

            t.Attributes.Add("onmouseout","ltmouseout(this)");

 

            //添加样式,用来控制字体

            t.Style.Add("font-size","10pt");

 

            //添加一行

            TableRow tr = new TableRow();

            //添加两个单元格

            TableCell tc1 = new TableCell();

            TableCell tc2 = new TableCell();

 

            //将控件添加到Controls集中.

            tc1.Controls.Add(label);

            tc2.Controls.Add(textBox);

            tr.Controls.Add(tc1);

            tr.Controls.Add(tc2);

 

            t.Controls.Add(tr);

 

            this.Controls.Add(t);           

        }

 

5、  本控件向外提供了三个属性,backgroundColor 表示鼠标移入控件的背景颜色,labelString 为标签内文本,textString 为文本框内容。定义如下:

private Label label = new Label();//创建一个标签

        private TextBox textBox = new TextBox();//创建一个文本框

private Color _backgroundColor;//鼠标移入的背景颜色

public Color backgroundColor

        {

            get

            {

                if(_backgroundColor == Color.Empty)

                    return Color.Blue;

                return _backgroundColor;

            }

            set

            {

                _backgroundColor = value;

            }

        }

 

        public string labelString

        {

            get

            {

                return label.Text;

            }

            set

            {

                label.Text = value;

            }

        }

 

        public string textString

        {

            get

            {

                return textBox.Text;

            }

            set

            {

                textBox.Text = value;

            }

        }

 

6、  这一步需要重写 CreateChildControls() 方法,该方法会自动被调用,用来生成子控件。

protected override void CreateChildControls()

        {

            this.EnsureChildControls();//如果子控件没有创建,则创建

            base.CreateChildControls ();//调用方法

            CreateControls();

}

 

7、  将第一步的脚本存到一个常量中:

private const string MOUSE_SCRIPT = //用来触发鼠标事件的脚本

            "<script language=javascript>/n" +

            "var OldColor;/n" + 

            "function ltmouseover(ctrl,color)/n" + //当鼠标移入时,调用该方法,ctrl为表格,color为要改变的颜色值

            "{/n" +

                "OldColor = ctrl.style.backgroundColor;/n" + //记录下原来的文档背景颜色

                "ctrl.style.color = ’#ffffff’;/n" +//将字体颜色改为白色

                "ctrl.style.backgroundColor = color;/n" + //更改表格背景颜色

            "}/n" +

            "function ltmouseout(ctrl)/n" + //鼠标移出时调用 ,参数同上

            "{/n" +

                "ctrl.style.backgroundColor = OldColor;/n" + //还原背景颜色

                "ctrl.style.color = ’#000000’;" + //将字体颜色还原成黑色

            "}/n" +

        "</script>/n";

 

重写OnPreRender()方法将该脚本输出到浏览器,考虑到同一页面可能会有多个该控件的情况,此时并不需要每个控件都生成一段脚本,而是所有控件共享这段脚本,所以,我们要用Page.IsClientScriptBlockRegistered()判断该脚本是否已经输出,如果输出,就不需要再次输出了。

protected override void OnPreRender(EventArgs e)

        {

            //将脚本输出到页面中.

            if(!Page.IsClientScriptBlockRegistered("mousescript")) //防止重复输出.

            {

                Page.RegisterClientScriptBlock("mousescript",MOUSE_SCRIPT);

            }

            base.OnPreRender (e);

}

    

    控件的使用

    使用控件显然比创建控件简单得多,下面讲一下该控件的使用方法(适合初学者):

    将该项目编译后,会生成一个叫LabelTextbox.dll的程序集。

    

    1、创建一个测试项目,打开工具箱,点击右键,选择“添加/移除项”,如下图:

     2、弹出“自定义工具箱”,选择“.net FramWork组件”选项卡,点击“浏览”,在LabelTextBox工程目录下找到“LabelTextbox.dll”程序集,如图:

3、确定后,在工具箱中出现该控件的图标,如下图:

 4、直接将该控件拖到WEB窗体就能使用了。

 

       本程序调试环境:Windows2000 Server、MS.NET2003。

ASP.NET组件设计Step by Step(1-3)
www.xyhhxx.com  发布者: seo  时间: 2005-11-23


转自CSDNBlog  之cashcho的专栏 


学习创建工程库、在自己的项目中应用 

1、  启动VS200X 

2、  选择新建解决方案 

3、  选择缺省的方案类型,选择 Web控件库。Web控件库不需要web站点,基本上是一个DLL类型库 

4、  输入自己的类型库的名称,譬如ASPCTLLib之类的。这个决定了namespace和以后其他工程使用此库的引用库名。 

5、  系统自动产生的代码如下: 

using System; 

using System.Web.UI; 

using System.Web.UI.WebControls; 

using System.ComponentModel; 

  

namespace ASPCtlLib 



     /// <summary> 

     /// WebCustomControl1 的摘要说明。 

     /// </summary> 

     [DefaultProperty("Text"), 

          ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")] 

     public class WebCustomControl1 : System.Web.UI.WebControls.WebControl 

     { 

          private string text; 

          [Bindable(true), 

              Category("Appearance"), 

              DefaultValue("")] 

         public string Text 

         { 

              get 

              { 

                   return text; 

              } 

              set 

              { 

                   text = value; 

              } 

         } 

  

         /// <summary> 

         /// 将此控件呈现给指定的输出参数。 

         /// </summary> 

         /// <param name="output"> 要写出到的 HTML 编写器 </param> 

          protected override void Render(HtmlTextWriter output) 

         { 

              output.Write(Text); 

         } 

     } 




 


6、  AssemblyInfo.cs的介绍 

7、  编译此工程得到ASPCTLLIb.dll 

8、  其他项目引用 

  

如何引用自己建立的Web控件库 

1、  打开/新建一个asp.net项目 

2、  引用ASPCTLLib.dll,将会在引用中出现aspctllib,同时将之前的dll文件复制到当前工程的目录 

3、  将自己的Web控件库中控件加入toolbar的选项卡:右击选项卡,自定义工具箱,选择.net框架组件,浏览,找到aspctllib.dll加入,即可看到名为WebCustomControl1的组件存在于工具箱 

4、  打开自己的aps.net web 项目,选择一个web窗体,可以将自己的控件加入到其中。缺省的设计时此控件会显示:[WebCustomControl1 ” WebCustomControl1”]。 

5、  选中窗体中的此控件,打开属性进行编辑,基本上缺省的设计控件仅有一个Text属性属于定制属性,输入特定文字。 

-------------------------------------------------------------------------------
ASP.NET组件设计(2)  
从继承关系上看,Asp.net 服务器控件的类别大致上分为4类: 

1、  用户控件 

类似page,基本上不需要编程,可以将某个aspx页面作为复用组件使用。 

  

2、  LiteralControl 

ASP.NET 将所有不需要服务器端处理的 HTML 元素和可读文本编译为该类的实例。例如,开始标记中不包含 runat="server" 属性/值对的 HTML 元素被编译为 LiteralControl 对象。 

文本控件的行为与文本容纳器一样,这意味着可以从文本控件提取文本,并通过父服务器控件的 Controls 属性从父服务器控件的 ControlCollection 中移除文本控件。因此,当开发从 LiteralControl 类导出的自定义控件时,确保由控件自己执行任何所需的预处理步骤,而不是使用对 LiteralControl.Render 方法调用的调用来实现它们。一般都会这样做以提高 Web 应用程序的响应时间。 

LiteralControl一般不会触发服务器端事件。 

可以以编程方式分别使用 ControlCollection.Add 或 ControlCollection.Remove 方法,从页或服务器控件添加或移除文本控件。 

  

3、  HtmlControl 

HTML元素的asp.net模型服务器端对应组件。每一个HtmlControl直接对应于特定的HTML元素(不一定是输入元素)。 

4、  WebControl 

通常的Web控件。可以理解为属于复杂的HTML元素以及服务器端处理逻辑组成的。 

  

继承关系图: 

System.Object 

   System.Web.UI.Control 

        System.Web.UI.TemplateControl 

               System.Web.UI.Page 

        System.Web.UI.UserControl 

        System.Web.UI.LiteralControl 

        System.Web.UI.HtmlControl 

        System.Web.UI.WebControl 

  

通常需要程序员开发的属于用户控件、web控件。xxx 

从WebControl继承而来的web服务器组件,往往继承了丰富的UI元素和控制能力。 

  

用户控件 

1、  为什么是用户控件 

现代人喜好偷懒,喜欢“所见即所得”以及“拖放”式的编程。在一个阿asp.net web项目中往往会在多个页面中存在共用的UI,譬如header footer等,如果所有页面的内容都一致,往往采用包含特定的js文件来实现,但如果这些多个页面都需要的UI部分跟随不同的用户状态等不同而会不同,这是必须采用动态逻辑处理,普通的js/html就无法满足要求。之前asp中采用include asp文件来实现,现在asp.net改变了包含asp文件的种种缺限而采用用户控件来解决。 

用户控件本质上是一个独立的asp.net文件,扩展名为ascx。用户控件通常依赖于特定的asp.net web项目。当使用用户控件时,asp.net页面解析器从aspx文件中动态生成一个类,将其编译到一个特定的装配件(临时缓存中托管dll),并按照.net方式引用此类实例,并进行处理。 

  

  

2、  如何得到用户控件 

得到用户控件,在vs中实际上仅需要: 

在web项目中添加—〉用户控件—〉输入用户控件名—〉在得到的空白页面进行UI设计,并存储。 

使用时,将用户控件拖放在web 窗体上,即可使用用户控件。此时vs替代我们做了一下工作: 

在使用用户控件的页面添加一个register指令。具体为: 

<%@ Register TagPrefix="uc1" TagName="WebUserControl1" Src="WebUserControl1.ascx" %> 对于页面解释器可解释为:将<uc1: WebUserControl1 作为用户控件来解释,uc1为标签前缀(类似标准web控件的asp标签前缀),WebUserControl1作为此用户控件的标签,遇到这一组合就解释为使用用户控件实例。Src值为该用户控件源文件所在的虚拟路径,但不可以为绝对路径。 

  

3、  用户控件的实质(asp.net的用户控件的背景) 

asp.net是编译的,处理用户控件同处理页面是类似的,页面中所有元素都是编译成特定类被asp.net页面类调用、触发事件的。用户控件也是通过此途径被复用的。当处理用户控件时,页面框架执行以下步骤: 

解释ascx文件,相应解释成为一个派生于System.Web.UI.UserControl的托管类 

动态编译到自动生成的装配件中。 

以上过程仅在第一次使用用户控件时发生,以后复用此用户控件会省略此步骤。页面使用register指令,将会引用此类。页面中的用户控件会成为此类的实例。实际上用户控件也是编写asp.net类,派生于System.Web.UI.UserControl的类,页面使用这些类如同使用ASP.net的标准类,为以不同在于标准控件类内置可直接实例化使用,而用户控件需要在使用前进行类似页面的编译工作,编译得到类并组装到装配件后可以被实例化。 

  

4、  用户控件编程注意事项 

用户组件不可以通过new方式实例化,因为用户控件类属于动态生成,new的时候(这个时候应当是页面编译时候,用户控件还没有被运行使用),用户控件类还不存在。但是可以通过Page.LoadControl(string controlname)来装载,此时为了loadControl可以找到用户控件,需要采用refence指令:<%@ Refence Control=”xxx/UserControlSamples.ascx”%> 。 

用户控件可以被另外用户控件嵌套使用,但一定要确保用户控件引用位置正确。 

用户控件本身属于UI,可以被缓存,通过ascx文件顶端的OutPutCache指令来确定。  
        用户控件代码中到的URL是指用户控件存在的路径,而不是调用用户控件的页面代码的路径,需要通过Page.ResolveUrl进行正确的解析。 

-------------------------------------------------------------------------------------

ASP.NET组件设计(3) 

自定义控件(custom control)入门 

在服务器端中执行程序逻辑的组件,是asp.net 应用程序的基本构造块。为了能够在asp.net环境执行,必须满足一定条件。 

        为了能够参与到网页框架执行,需要满足IComponent, IDisposable, IParserAccessor,   IdataBindingsAccessor等接口。所以框架提供System.Web.Control类供控件开发者继承。 

IComponent     提供组件所需的基本功能,接口实现为: 

        { 

        ISite Site {get; set;}//提供站点接口,组件可以通过此接口访问容器 

        event EventHandler Disposed;        //事件 

} 如果实现了此接口,就成为可设计组件,可加入到可视化设计器工具箱,能够脱放到页面(Isite接口)中,属性浏览器中显示属性。 

IDisposable  接口实现组件自身资源释放(Icomponent实际上继承自IDisposable  ) 

IParserAccessor 该接口规定AddParsedSubObject(object obj)方法用于通知服务器控件已分析元素(XML 或 HTML)。分析元素时,元素被识别为实现该接口的服务器控件的子级。这些元素将被转换为对象。实现该接口时,所创建的控件在被通知已分析元素后立即定义要发生的处理。 

IDataBindingsAccessor 接口允许在设计时访问控件的数据绑定表达式集合。 



       DataBindingCollection DataBindings {get;}// 指示控件的所有数据绑定的集合。此属性为只读。 

bool HasDataBindings {get;} //返回控件是否包含任何数据绑定逻辑。 



  

       继承自Control的WebControl类为表现HTML(毕竟最后用户看到的是HTML)而在Control类中加入了一些新的功能,如样式、字体、背景、前景。。。 

  

Control类的定义: 

       公共属性: 

ClientID 获取由 ASP.NET 生成的服务器控件标识符。  

Controls 获取 ControlCollection 对象,该对象表示 UI 层次结构中指定服务器控件的子控件。  

EnableViewState 获取或设置一个值,该值指示服务器控件是否向发出请求的客户端保持自己的视图状态以及它所包含的任何子控件的视图状态。  

ID 获取或设置分配给服务器控件的编程标识符。  

NamingContainer 获取对服务器控件的命名容器的引用,此引用创建唯一的命名空间,以区分具有相同 Control.ID 属性值的服务器控件。  

Page 获取对包含服务器控件的 Page 实例的引用。  

Parent 获取对页 UI 层次结构中服务器控件的父控件的引用。  

Site 获取有关服务器控件所属 Web 站点的信息(原文自MSDN,但是我认为不对,应该是指组件的“容器”站点,并非 web site)。  

TemplateSourceDirectory 获取包含当前服务器控件的 Page 或 UserControl 的虚拟目录。  

UniqueID 获取服务器控件的唯一的、以分层形式限定的标识符。  

Visible 获取或设置一个值,该值指示服务器控件是否作为 UI 呈现在页上。 

  

公共方法 

DataBind 将数据源绑定到被调用的服务器控件及其所有子控件。  

Dispose 使服务器控件得以在从内存中释放之前执行最后的清理操作。  

Equals(从 Object 继承) 已重载。确定两个 Object 实例是否相等。  

FindControl 已重载。在当前的命名容器中搜索指定的服务器控件。  

GetHashCode(从 Object 继承) 用作特定类型的哈希函数,适合在哈希算法和数据结构(如哈希表)中使用。  

GetType(从 Object 继承) 获取当前实例的 Type。  

HasControls 确定服务器控件是否包含任何子控件。  

RenderControl 将服务器控件的内容输出到所提供的 HtmlTextWriter 对象中;如果已启用跟踪功能,则存储有关控件的跟踪信息。  

ResolveUrl 根据传递给 TemplateSourceDirectory 属性的值,将相对 URL 解析为绝对 URL。  

ToString(从 Object 继承) 返回表示当前 Object 的 String。 

  

       公共事件 

DataBinding 当服务器控件绑定到数据源时发生。  

Disposed 当从内存释放服务器控件时发生,这是请求 ASP.NET 页时服务器控件生存期的最后阶段。  

Init 当服务器控件初始化时发生;初始化是控件生存期的第一步。服务器控件应执行任何创建和设置实例所需的初始化步骤。在该事件内无法使用视图状态信息;它尚未填充。在该事件的生存期内不应访问其他服务器控件,不论它是此控件的子级还是父级。不一定会创建其他服务器控件,也不一定能够访问它们。 

Load 当服务器控件加载到 Page 对象中时发生。通知服务器控件执行任何设置为在每次页请求时发生的处理步骤。开发者可以访问视图状态信息并利用该事件形成 POST 数据。还可以访问页控件层次结构内的其他服务器控件。 

PreRender 当服务器控件将要呈现给其包含的 Page 对象时发生。使用该事件在服务器控件呈现给页的输出之前执行任何更新。在该事件的生存期内可以保存服务器控件视图状态的任何更改。不保存呈现阶段内所做的同样更改。 

Unload 当服务器控件从内存中卸载时发生。 

  

              受保护的属性和方法有: 

              保护的属性 

ChildControlsCreated 获取一个值,该值指示是否已创建服务器控件的子控件。  

Context 为当前 Web 请求获取与服务器控件关联的 HttpContext 对象。  

Events 获取控件的事件处理程序委托列表。此属性为只读。  

HasChildViewState 获取一个值,该值指示当前服务器控件的子控件是否具有任何已保存的视图状态设置。  

IsTrackingViewState 获取一个值,该值指示服务器控件是否将更改保存到其视图状态。  

ViewState 获取状态信息的字典,这些信息使您可以在同一页的多个请求间保存和还原服务器控件的视图状态。  

ViewStateIgnoresCase 获取一个值,该值指示 StateBag 对象是否不区分大小写。  

受保护的方法 

AddParsedSubObject 通知服务器控件某个元素(XML 或 HTML)已经过语法分析,并将该元素添加到服务器控件的 ControlCollection 对象。  

ClearChildViewState 删除服务器控件的所有子控件的视图状态信息。  

CreateChildControls 通知使用基于合成的实现的服务器控件创建它们包含的任何子控件,以便为回发或呈现做准备。  

CreateControlCollection 创建一个新的 ControlCollection 对象来保存服务器控件的子控件(包括文本控件和服务器控件)。  

EnsureChildControls 确定服务器控件是否包含子控件。如果不包含,则创建子控件。  

Finalize(从 Object 继承) 已重写。允许 Object 在“垃圾回收”回收 Object 之前尝试释放资源并执行其他清理操作。  

IsLiteralContent 确定服务器控件是否只包含文字内容。Asp.net页面中普通的html标签被asp.net编译为一个 LiteralContent控件(意味着轻型控件)。 

LoadViewState 从 SaveViewState 方法保存的上一个页请求还原视图状态信息。  

MapPathSecure 如果请求服务器控件有足够的安全权限读取映射结果,检索相对于源文件的映射物理文件路径。  

MemberwiseClone(从 Object 继承) 创建当前 Object 的浅表副本。  

OnBubbleEvent 确定服务器控件的事件是否沿页的 UI 服务器控件层次结构向上传递。  

OnDataBinding 引发 DataBinding 事件。  

OnInit 引发 Init 事件。  

OnLoad 引发 Load 事件。  

OnPreRender 引发 PreRender 事件。  

OnUnload 引发 Unload 事件。 注意   在服务器控件生存期的此阶段,服务器控件应执行所有最后的清理操作,例如关闭文件、关闭数据库连接和丢弃对象。  

RaiseBubbleEvent 将所有事件源及其信息分配给控件的父级。  

Render 将服务器控件内容发送到提供的 HtmlTextWriter 对象,此对象编写将在客户端呈现的内容。  

RenderChildren 将服务器控件子级的内容输出到提供的 HtmlTextWriter 对象,此对象编写将在客户端呈现的内容。  

SaveViewState 保存自页回发到服务器后发生的任何服务器控件视图状态更改。  

TrackViewState 导致跟踪服务器控件的视图状态的更改,以便这些更改可以存储到服务器控件的 StateBag 对象中。通过 Control.ViewState 属性可访问此对象。 

  

  

了解了Control这个“基石”,我们可以了解到,编写服务器组件就是在此基石上建造,同时我们可以调用的方法、数据,可以截获的事件都列出,以上数据成员是我们编写服务器控件的“输入数据”(当然还可以有其他输入数据,但是主要数据在此);以上方法是帮助我们操控控件以及控件同外部交互,事件为我们“感知”外部交互提供时机;另外,服务器控件也需要被容器调用相关方法(譬如render),我们可以重写这些方法,修改Control的缺省GUI表现。 

ASP.NET组件设计Step by Step(4-6)
www.xyhhxx.com  发布者: seo  时间: 2005-11-23


转自CSDNBlog  之cashcho的专栏

ASP.NET组件设计Step by Step(4) 


如果我们的服务器控件需要较完备的GUI,譬如需要控制HTML元素的附加属性字体、颜色、高度等,那么应当从Control的子类WebControl继承,以得到所需的基础性能。从WebControl类派生,需要遵守通常如下的约定:

1、  包含从System.Web.UI.WebControl的命名空间的引用

2、  不能够重载Render方法直接向输出流提供数据,而应当重载RenderContents方法来实现

3、  通常情况下,WebControl的派生类最终在输出流中表现为<span>标签,但是如果自己希望使用其它的标签,就应当重载WebControl的tagKey属性或者TagName属性。

经验(或者说MS建议):

1、  如果控件生成非可视化元素或显示给非HTML客户端,那么应当选用Control作为符类继承。如<meta><xml>等标签

2、  提供HTML界面的从WebControl继承

3、  扩展修改功能时应当从一个已存控件派生,但是不要从System.Web.UI.HtmlControls命名空间派生,因为vs.net设计器不承认从该类派生的控件

为了能够支持设计器,实现设计期,那么需要考虑attribute(元数据)的编程。

 

 

 

视图状态

       web编程很重要的一个方面是状态管理,也就是解决在无状态的http协议基础上的状态管理问题。在asp类web编程技术时代,这通常通过以下技术手段来解决:

1、  Session

2、  Cookies

3、  隐藏变量

4、  URL携参

然而,以上方案都有自身的缺陷,譬如Session不宜扩展,Cookies在特定情况下可能不可用,隐藏变量不易管理,URL携参有长度限制且容量有限。。。

综合考虑,MS提出了基于隐藏变量方案的ViewState(视图)的概念,通过视图保存2个asp.net页间的服务器端控件状态。视图是如何工作的呢?

在处理一个Web请求后,页面框架会收集页面控件树中所有控件的状态并且创建一个视图对象。每一个Control都有一个ViewState字典保存自己的状态,当输出html到客户时将全部ViewState进行串行化为一个字符串表达式,作为隐含变量发送到客户端,通常情况下我们将视图的串行化子符串在服务器同客户端间进行传递,上一个请求的隐含变量会回传到服务器端进行并行化,然后“还原”给服务器控件。本质上,是一个隐含变量,但是在隐含变量基础上加入了asp.net的管理功能,这就是视图的本质。

Control的EnableViewState决定了控件是否同意将自己的状态交给自动化的视图管理。

当一个控件需要保存状态时候,没人可以支持保存的属性为int32 boolean 等“简单原生”数据类型。如果是较为复杂的属性值类型,需要编程者提供类型转换器,将值数据转换为字符串,若未提供,那么采用代价高昂的二进制串行化功能(逐个byte的进行)。视图数据在传递过程中通过传递一个附加的摘要来确保数据不被篡改(但无法保证不被窥探,毕竟base64编码近乎明文)。

------------------------------------------------------------------------

ASP.NET组件设计Step by Step(5) 
控件生存周期

 

当一个asp.net页面被请求后,一个page实例被生成,开始自己的逻辑,最终返回HTML流给用户端。构成逻辑处理的是page中存在的服务器端控件以及控件间的交互,并且在页面结束前服务器控件们被销毁(视.net的回收策略而定)。那么这些控件在短短的页面处理过程中经历了创建、处理、销毁等到底是如何组织的?这些控件是如何同其他控件交互,如何在多个页面间保持状态的?(这应当是整个控件开发的基本也是重要的知识)

首先,我们看看特殊的控件,也就是Page类,所有aspx页面的父类(或者祖先)。因为这是我们程序员主要的舞台。Page类继承自TemplateControl, 且实现了IhttpHandler。IhttpHandler接口是保证页面被asp.net框架所调度,并且可以获得HTTP协议的数据输入流以及获得向HTTP输出流输出数据的能力;而TemplateControl类是继承自Control类。Page实现了InamingContainer接口,这保证了他可以充当页面中的控件们的容器(控件们的战斗舞台)

 

好了,现在看看控件的生命周期(看看MS是如何定义这个框架体系的):

1、  Instance 实例化

通过控件的构造器所实例化。还可以通过被父控件实例化而生成。

 

2、  Initialize 初始化

控件会通过默认方式调用OnInit方法,从而引发On_init事件。Page根据aspx页面的语法以及标签设定值来初始化控件,对声明语法中的控件及其属性赋值。作为一个特殊控件,一般可以在Page的OnInit事件中允许编程者提供控间的初始化操作(对某些属性赋值)。对于控件包含的子控件,控件可以访问他们,但是子控件是不可以访问父控件的(因为控件此时还没有被加载(Load))。

 

3、  Begin Tracking View State 开始跟踪视图状态

发生在初始化阶段末尾,Page会调用控件的TrackViewState方法(这是一个继承自Control的保护方法)

 

4、  Load View State 加载视图

此时,页面框架自动恢复了ViewState字典(ViewState数据来自表单form中的隐含字段),控件会根据ViewState值来设定自己的属性或者内部字段变量等

 

5、  Load PostBack Data加载回传数据

如果控件实现了IpostBackDataHandle接口,那么页面回调用控件实现的接口,让其参与对回传数据的处理

 

6、  Load 加载

此时,控件树(page的控件以及控件的子控件构成的树)所有控件都已经被初始化,并恢复到上一个周期的状态(这是通过ViewState获得的),可以访问其他的任何控件。

 

7、  Raise Change Events引发修改(控件的)事件

处理回传的数据,此时可能会引起控件的某些事件作为对某些属性被修改的通知。

 

8、  Raise Postback Event 引发回传数据

当发生修改事件时候,引发将客户端发生的一些事件映射到服务器控件的事件,从而调用控件的事件的处理例程。这大多是控件开发者的客户—另外一些程序员重用控件时的舞台。

 

9、  PreRender 预生成

通过调用控件的OnPreRender方法,执行在生成控件前的所需任何工作。递归调用子控件的此方法。

 

10、Save View State 保存视图状态

       控件继承Control的方法来保存当前控件状态到ViewState中去

       

11、Render 生成

       控件输出HTML数据到HTML流中去。

       

12、Unload 卸载

       页面通过实现Page_Onload方法执行清除工作,也默认引发控件的Unload事件

 

13、Dispose释放

       此时,控件执行清除占用资源的方法。

 

以上讨论适宜于在aspx页面中声明创建的控件,如果是在页面/控件的事件处理程序中创造得控件,则在控件加入到控件树开始执行各个阶段,直到达到页面的当前阶段,之后,该动态创建的控件将随同页面其他控件一样工作。

-------------------------------------------------------------------------------

ASP.NET组件设计Step by Step(6) 
回传事件映射到服务器端事件


        asp.net页面如果是通过post请求到服务器,框架将会遵行事件周期生成、调用控件,而控件(如果支持回传)则将加载回传数据,并且映射成控件的服务器端事件,就好像在重放客户的客户行为(客户按下一个按钮,却引发服务器端控件的click事件)。其中的机制如何?

    如果一个控件需要处理回传事件,那么必须实现一个特定接口 IPostBackEventHandler接口:

public interface IPostBackEventHandler{

    void RaisePostBackEvent(string eventArgument);

}

以及另外一个接口:

IPostBackDataHandler 

{

bool LoadPostData(  string postDataKey,   NameValueCollection postCollection);

void RaisePostDataChangedEvent();

}

    一旦控件实现了这些接口,页面框架就会自动在PostBack数据完成后,调用控件的此接口IPostBackDataHandler。LoadPostData,从而让控件读取post上来的数据。PostDataKey为PostBack数据中的命名键名,通过NameValueCollection[postDataKey]可以获得页面框架传递给控件的值。控件应当读取此值,进行自己内部状态更新,反映状态变化。如果控件返回真,表示服务器控件状态改变,此时页面框架会立即调用此控件的RaisePostDataChangedEvent方法。此时,控件应当自己定义该引发那些服务器控件对外提供的事件。这些事件往往是控件编程者精心设计留给使用这控键的aspx编程者的代码舞台。

    至于另一个接口IPostBackEventHandler,也是回传时候服务器调用的接口。我们知道每一个控件都有一个UniqueID,当客户端触发一个可以引起回传的客户端事件(譬如按下了submit按钮),那么自然HTTP Post数据到服务器端,服务器页面框架进行到PostBack处理时候,会检索控件是否支持IpostBackEventHandler接口,并且查找控件的UniqueID发现支持就立即调用此接口的RaisePostBackEvent方法,表示UniqueID的控件发生了一个需要捕获的事件。很明显,并非客户端所有事件都可以投射到服务器端,只能够是能够引发Post Back(也就是能够提交表单数据到服务器的事件)的事件和控件。注意,这里严格要求了UniqueID必须在服务器端和客户端对应一致,否则无法映射事件。

值得控件编写者注意的是,如果要实现接口,需要如下实现接口,而不是通常的仅仅接口方法名同名即可:

       void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)

       {

              ……

       }

    也就是说,接口针对页面框架实现的,也由页面框架调用。

另一方面,在客户端,能够引起回传的实际上仅有2个HTML元素<inoput type=submit >和<input type=image,但是通过客户端脚本,其他客户端事件也可导致回传数据到服务器端。Page类提供了一系列的方法来帮助实现其他可引发回传的途径:

 

public string GetPostBackClientEvent(

   Control control,

   string argument

);

获取对客户端脚本函数的引用,该函数在被调用时将导致服务器回发到窗体。

 

public string GetPostBackClientHyperlink(

   Control control,

   string argument

);将 javascript: 追加到从 GetPostBackEventReference 调用的返回的开头,从而使服务器上可以进行超级链接回发处理。

 

 public string GetPostBackEventReference(Control);

public string GetPostBackEventReference(Control, string);

获取对客户端脚本函数的引用,调用该函数将使服务器

 

    如果一个控件确定通过以上方法确保客户端引发回传事件,那么控件引用页将导致最终输出到客户端的HTML中包含脚本,并且脚本中藏有如下隐含变量:

<input type=”hidden” name=”__EVENTTARGET” value=””>

<input type=”hidden” name=”__EVENTARGUMENT” value=””>

<script language=”javascript”>

<!—

function __doPostBack(eventTarget,eventArgument)

        {

        var theform=document._ct10;

        theform.__EVENTTARGET.value= eventTarget;

        theform.__EVENTARGUMENT.value= eventArgument;

        }

-- >

</script>

任何客户端脚本只要合法调用了__doPostBack方法即可实现回传到服务器端,并且服务器端也知道了是引发了那一个UniqueID得控件的事件。

ASP.NET组件设计Step by Step(7-9)
www.xyhhxx.com  发布者: seo  时间: 2005-11-23


ASP.NET组件设计Step by Step(7) 


复杂属性和状态管理

复杂属性声明/持久:

所谓复杂属性,即通常不可用CLR的基本数据类型表达的属性。譬如,FontInfo,即代表字体属性,包含Bold 、Name、Size、Color等信息(通常称为子属性)。

对于复杂属性,控件在声明时候,通常采用 属性—子属性 的连字符表示复杂属性的一个子属性。

<sdp:TextBox id=”Textbox1” Font-Name=”Vender” Font-Size=”12pt” runat=”server” />

其中的划线部分即代表了Font属性。

复杂属性的另外一种表达是,将复杂属性嵌套放于控件声明标签之中,乘坐 内部属性持久性(inner property persistence )。如:

<asp:DataGrid runat=”Server”>

<HeaderStyle ForeColor=”Red” />

   </asp:DataGrid >

 

复杂属性序列化/持久化的设计时支持

设计时实现是在控件之外实现的,利用元数据(attribute)来实现。对于连字符实现的子属性,经过设定特定的attribute,即可影响设计器从而实现设计时支持。

DesignerSerializationVisibility 实际上在asp.net中是指DesignerSerializationVisibilityAttribute的属性类,通过元数据中指定,并且设为:

DesignerSerializationVisibility.Content可以让设计器知道,应当将复杂属性的内容进行序列化而不是属性本身

DesignerSerializationVisibility.Hidden       让设计器忽略属性和属性内容

DesignerSerializationVisibility.Visible       让设计器以正常方式序列化

       所谓正常方式,就是以字符传、整型如果不属于这些就用字节方式持久化属性(当然对于复杂属性这相当划不来)。

       NotifyParentProperty(true) 表示属性浏览器对控件得此复杂属性的修改可以通告给控件,并告知之上的各个对象(通常会给设计器应用程序),告知属性已经“脏”了。这些约定对控件设计者和设计器开发者以及asp.net页面的开发者都很重要。

       

对于标签嵌套方式的属性持久,应当采用ParseChilren(true)来指定属性的持久,设计器实现嵌套方式则应当指定PersistChildren(false)让设计器将嵌套标签转化为复杂属性的一部分(而不是内嵌的子控件)

 

类型转换器

为了解读复杂属性的持久形式为设计运行时形式,需要对复杂属性指定类型转换器,基本目标是将持久形式解读转换为CLR支持的类型,同时也将属性的CLR类型表达为设计时、运行时的持久形式。即将Font-Size=”8pt”转换为int32 的8,将复杂属性的8持久化为Font-Size=”8pt”.

类型转换器的实现不依赖于任何用户界面功能。因此,可在 Windows 窗体和 Web 窗体中应用同一个类型转换器。

类型转换器继承自System.ComponentModel.TypeConverter。

实现类型转换器的步骤:

1、  定义一个从 System.ComponentModel.TypeConverter 派生的类。 

2、  重写 CanConvertFrom 方法,指定转换器可从中转换的类型。此方法是重载方法。 

3、  重写实现转换的 ConvertFrom 方法。此方法是重载方法。 

4、  重写 CanConvertTo 方法,指定转换器可转换为的类型。转换为字符串类型不需要重写此方法。此方法是重载方法。 

5、  重写实现转换的 ConvertTo 方法。此方法是重载方法。 

6、  重写执行验证的 IsValid 方法。此方法是重载方法。

通过TypeConverter(typeof(somecustimoziedTypeConverter))的方式确定某个属性同特定的类型转换器相关。

 

对于asp.net应用来讲,如何保持两个页面间的控件的复杂属性很重要。Control类的默认状态管理交给ViewSatte字典,而复杂属性,使用ViewState时不可能的,这必须用到IstateManager接口参与状态管理。

Interface IstateManager

{

       public bool IsTrackingViewState ;  //当由类实现时,获取一个值,通过该值指示服务器控件是否正在跟踪其视图状态更改。 

       void LoadViewState(   object state); //当由类实现时,加载服务器控件以前保存的控件视图状态。 

object SaveViewState(); //当由类实现时,将服务器控件的视图状态更改保存到 Object。 

void TrackViewState();//当由类实现时,指示服务器控件跟踪其视图状态更改。

}

Control的ViewSatte属性实际上就是一个复杂属性,本质上是一个字典类,通过实现了IstateManager接口参与状态管理(ViewState的细节):

1、  ViewState的数据类型是复杂属性System.Web.UI.StateBag类。实现IstateManager接口的字典。保存多项键/值对,键为字符串,值为对象,当某个对象自动添加到State把实例即ViewState中去时,自动加入一个StateItem对象,存有实际的属性值和相关的布尔标志,知识此属性(StateItem)在初始化后修改过。

2、  在接口的TrackViewState中将此函数的值对应到StateBag的内部布尔变量,如果为真,则当StateItem添加时候(跟踪一个对象的ViewState),或者修改行为,都将StateItem标记为修改过。

3、  SaveViewSate中,枚举项目,创建并返回两个ArrayList分别标记修改过的键和值。

4、  LoadViewSatet执行SaveState的逆操作。

当在ViewSatte中保存属性时候,其状态会自动维护。可以在ViewState中保存任何控减灾往返传送时需要的数(而不是全部数据,从效率考虑剔除冗余数据),且任何保存在ViewState红的数据必须时通过LosFormatter串行化的

所有复杂属性的状态保存应当实现IstateManager来将复杂属性转换为可以在ViewState中保存的简单属性,并且也可以ViewState中保存的简单属性恢复组合为复杂属性。
------------------------------------------------------------------

ASP.NET组件设计Step by Step(8) 
控制控件的样式

 

控件最终通常要生成HTML代码在客户端,这些HTML元素可以采用丰富的CSS样式。你当然可以直接进行CSS 设定,但是asp.net给控件开发者提供了编程方式控制样式的途径。

如果对样式无特殊要求,直接继承webControl的样式功能即可,如果需要修改或者扩充继承的样式功能,则需要深入了解控件样式的背景知识

WebControl的样式功能全部封装在ControlStyle属性中(一个名为Style System.Web.UI.WebControls.Style的属性)。所有样式属性都是ControlStyle属性的子属性。WebControl的ControlStyle的定义为:

Private Style _contentStyle;

……

public Style ControlStyle

{

       get

              {

                     if(_contentStyle = = null)

                            {

                                   _contentStyle=CreateControlStyle();

                                   if(IsTrackingViewState)

                                          {

                                                 ((IStateManager)_controlStyle).TrackViewState();

}

                            }

                     return  _contentStyle;

              }

}

我们看到,ControlStyle是只读属性,在第一次访问时被创建(这个思想继承了.net的JIT方案)。

那么这个CreateControlStyle到底是什么回事?

Protected virtual Style CreateControlStyle()

{

       return new Style(ViewSatte);

}

原来也是读取ViewState得到的。这样,无论是你从.aspx页面中关于控件声明中定义的还是通过编程设定的都可以在存取时反映得到。

作为控件开发者,可以自定义继承自Style的属性,例如,MyTable控件定义一个TableControl类型,添加Table支持的cellpadding/CellSpaceing等属性。

编程控制属性有3种途径:

1、  覆盖受保护的虚函数CreateControlStyle

2、  利用ApplyStyle(Style s)方法将自定义的属性复制到控件自己的ControlStyle中去

3、  MergeStyle(Style s)合并方法到ControlStyle中去

 

我们看到,控件的ControlStyle属性和其他子属性都是公用一个StateBag的。因为控件style生成时候是调用传递ViewState的构造函数。另外,子控件也是采用同一个StateeBag来存储状态的。

 

 

复合控件

首先明确复合控件不同于用户控件,因为它是编译后的形式出现的,而用户控件则以文本形式部署。但共同点都是类复用来复用他们的功能。

复合控件包含多个已存控件,复用子控件提供的功能。譬如,当要编写的复合控件包含TextBox时候,就不必自己实现IPostBackDataHandler接口。复合控件可以派生子Control类或者WebControl类,复合的要点是:

1、  重载CreateChildControl方法来对子控件进行实例化、初始化,并将子控件添加到控件树中(加入到page的控件树从而获得控件的生命周期)。需要避免的是不可在OnInit事件中执行业务逻辑。

2、  实现System..Web.UI.InamingContainer接口,从而在复合控件下建立一个新的命名范围。InamingContainer仅仅是一个标记接口,让框架自动实现子控件的唯一命名。

为什么必须在CreateChildControls方法中创建子控件呢?实际上,这样做是为了可以在控件生命周期中任何需要的时候来创建子控件,而且可以利用子控件来处理诸如会传数据等任务。为了确保子控件在代码访问其之前创建好,Controllei定义的EnsureChildControls保护方法来检查子控件是够已经创建好,如果没有创建,,就可以调用CreateChildControls方法来创建。如果子控件没有在render之前被创建,那么缺省情况下visible为true的未被创建的子控件会被PreRender方法的默认实现调用EnsureChildControls。

 

复合可以重用,但是也会带来性能损失(例如子控件实例化等)。所以,需要在性能和易用之间权衡,要么复合控件,要么干脆自己编写完全生成控件。

 

复合控件视图状态如何工作?

Control内建了跟踪、保存和恢复子控件的状态。

在开始跟踪视图状态阶段中,Control依次调用Controls集合中的控件的TrackVierState方法,跟踪子控件的状态。如果子控件是在父控件中打开状态下加入到Controls中,那么在添加到集合时候调用TrackViewState方法。

在保存视图状态阶段,Control首先调用SaveViewSate方法,默认情况下首先调用ViewState字典的SaveViewState。并保存所返回的对象,作为控件视图的第一部分;接下来,Control调用每一个子控件的SaveViewState,如果返回的子控件不为空,那么由Control在两个ArrayList中保存子控件的编号和对应状态,用来进行串行化。

在加载视图状态阶段,control先调用LoadViewState方法恢复上一次保存的状态第一部分,接下来Control访问Controls集合,将剩下的状态加载入子控件,一般通过编号和保存状态的ArrayList来组成,这样就恢复了控件及其子控件的转台。如果在此阶段还没有创建子控件,那么先保存子控件状态,留做以后使用,直到子控件创建后加载给子控件。

 

事件冒泡

ASP.NET 页框架提供一种称为“事件冒泡”的技术,允许子控件将事件沿其包容层次结构向上传播。事件冒泡允许在控件层次结构中更方便的位置引发事件,并且允许将事件处理程序附加到原始控件以及公开冒泡的事件的控件上。

例如:数据绑定控件(Repeater、DataList 和 DataGrid)使用事件冒泡将子控件(在项目模板内)引发的命令事件公开为顶级事件。虽然 .NET 框架中的 ASP.NET 服务器控件将事件冒泡用于命令事件(事件数据类是从 CommandEventArgs 派生的事件),但是,服务器控件上定义的任何事件都可以冒泡。

控件可以通过从基类 System.Web.UI.Control 继承的两个方法参与事件冒泡。这两个方法是:OnBubbleEvent 和 RaiseBubbleEvent。以下代码片段显示了这些方法的签名。

protected virtual bool OnBubbleEvent(

   object source,

   EventArgs args

);

protected void RaiseBubbleEvent(

   object source,

   EventArgs args 

);

RaiseBubbleEvent 的实现是由 Control 提供的,并且不能被重写。RaiseBubbleEvent 沿层次结构向上将事件数据发送到控件的父级。若要处理或引发冒泡的事件,控件必须重写 OnBubbleEvent 方法。

 

使事件冒泡的控件执行以下三种操作之一。 

1、控件不执行任何操作,此时事件自动向上冒泡到其父级。 

2、控件进行一些处理并继续使事件冒泡。若要实现这一点,控件必须重写 OnBubbleEvent,并从 OnBubbleEvent 调用 RaiseBubbleEvent。以下代码片段(摘自模板化数据绑定控件示例)在检查事件参数的类型后使事件冒泡。 

protected override bool OnBubbleEvent(object source, EventArgs e) {

            if (e  is CommandEventArgs) {                

                TemplatedListCommandEventArgs args =

                    new TemplatedListCommandEventArgs(this, source, (CommandEventArgs)e);

                RaiseBubbleEvent(this, args);

                return true;

            }

            return false;

        }

3、控件停止事件冒泡并引发和/或处理该事件。引发事件需要调用将事件调度给侦听器的方法。若要引发冒泡的事件,控件必须重写 OnBubbleEvent 以调用引发此冒泡的事件的 OnEventName 方法。引发冒泡的事件的控件通常将冒泡的事件公开为顶级事件。

protected override bool OnBubbleEvent(object source, EventArgs e) {

    bool handled = false;

    if (e is TemplatedListCommandEventArgs) {

        TemplatedListCommandEventArgs ce = (TemplatedListCommandEventArgs)e;

        OnItemCommand(ce);

        handled = true;

    }

    return handled;

}

 

模板化控件

使用模版化控件,控件开发者可以通过template指定生成的全部或者部分UI。模板是页面语法的一部分,可以包括静态的HTML(HTML语法表达的控件实际上是子控件,但是属于LiteralControl控件)以及其他文自文本的服务器控件。模板功能,允许将控制数据与其表示分开。模板控件本身不提供用户界面 (UI)。该控件的 UI 由页面开发人员通过内联模板提供,该模板允许页面开发人员自定义该控件的 UI。通过使用模板,控件生成不同于样式的UI,但是这种UI能力主要是产生页面元素。譬如repeater控件等。

要支持模板化,控件必须实现Itemplate接口。页面解析器解析模板标签内的文本,并生成一个解析树来表示模板的内容,就像解析整个Page一样。支持模板控件开发需要做到:

1、实现 System.Web.UI.INamingContainer 接口。这是没有任何方法的标记接口。它可以在您的控件下创建新的命名范围,这样子控件就在名称树中有了唯一的标识符。 

public class TemplatedFirstControl : Control,INamingContainer {...}

2、将 ParseChildrenAttribute 应用到控件,并传递 true 作为参数。在 ASP.NET 页上声明性地使用控件时,这样可指示页分析器如何分析模板属性标记。步骤 3 说明如何定义一个模板属性。 注意 如果您的控件是从 WebControl 派生的,则不需要应用 ParseChildrenAttribute,因为 WebControl 已经用该属性作了标记。

[ ParseChildren(ChildrenAsProperties = true)]

public class TemplatedFirstControl : Control, INamingContainer {...}

3、定义 System.Web.UI.ITemplate 类型的一个或多个属性。ITemplate 有一个方法 InstantiateIn,该方法可使用页上内联提供的模板创建控件。不必实现 InstantiateIn 方法;ASP.NET 页框架可提供这种实现。ITemplate 属性必须有 System.Web.UI.TemplateContainerAttribute 类型的元数据属性,它指出哪种 INamingContainer 控件将拥有实例化模板。这在步骤 4 中作了说明。如下代码所示定义了一个模板属性。 

[TemplateContainer(typeof(FirstTemplateContainer))]                 

 public ITemplate FirstTemplate {...}

4、TemplateContainerAttribute 的参数是您想在其中实例化模板的容器控件类型。容器控件独立于正在创作的模板控件。具有逻辑容器的原因是:模板控件通常有一个模板,该模板需要使用不同数据重复实例化。拥有与根模板控件不同的容器控件,使拥有多个此类示例成为可能。逻辑容器是该模板内子控件的即时 INamingContainer。在开发模板化数据绑定控件中更详细地介绍了这种关系。 

注意 容器控件本身必须实现 INamingContainer,因为它有需要在页上唯一命名的子控件。但是容器仅仅是容器,对应需要解释的模板内容,并非控件。

public class FirstTemplateContainer : Control, INamingContainer {...}

5、重写 CreateChildControls 方法以便在模板中创建子控件。这是通过三个步骤来完成的。 

实例化模板容器。 

调用模板属性的 InstantiateIn 方法并将该容器作为参数传递给它。InstantiateIn 方法(在 ITemplate 接口中声明)实例化该模板的元素,作为该模板容器的子控件。不必实现 InstantiateIn 方法;ASP.NET 页框架可提供这种实现。 

将模板容器的示例添加到您的模板控件的 Controls 集合。 

以下代码片段说明了 CreateChildControls 的实现。 

private Control myTemplateContainer;

protected override void CreateChildControls () 

{

   if (FirstTemplate != null)

   {

      myTemplateContainer = new FirstTemplateContainer(this);

      FirstTemplate.InstantiateIn(myTemplateContainer);

      Controls.Add(myTemplateContainer);

           }

    else

    {

        Controls.Add(new LiteralControl(Text + " " + DateTime));

    }

 }

5、重写从 Control 继承的 OnDataBinding 方法以调用 EnsureChildControls 方法。这样可保证在页框架尝试计算模板内任何数据绑定表达式之前,创建模板中的子控件。您还必须调用基类的 OnDataBinding 方法以确保调用已注册的事件处理程序。 

        protected override void OnDataBinding(EventArgs e) {

            EnsureChildControls();

            base.OnDataBinding(e);

        }

7、在步骤 5 中,在 CreateChildControls 方法内重复该逻辑,以便为控件的每个模板属性实例化一个模板。

我们看到,通常情况下我们在模板内指定的Container实际上需要我们控件开发者自行定义。实际上如果不重复生成子控件,InamingContainer也可不实现。但是开始提醒需要实现此接口。如果要在控件中支持数据绑定,那么模板容器应该由一个或者多个属性代表绑定的数据。通常模板类作为控件类的内部私有类实现。

控件可以重复实例化某个模板(只要在不同的容器实例中即可),因此,模板不应该包含或者假定任何控件实例作为成员变量,因为在每次模板实例化时候,成员变量都可以用新的值来重写。

-----------------------------------------------------------------------

ASP.NET组件设计Step by Step(9) 
先来分析asp.net处理一个web请求的过程。学习asp.net技术,除了翻看人家的文档(这是最基本的,知识就是知识,不懂的基本知识无法深入学习),还应当自己学会琢磨,动手实习。

当我们从客户端发出一个web请求后,到达web服务器,由web服务器处理或者转给asp.net框架处理。如果不存在web服务器(采用asp.net的缺省web处理),那你可以参鄙人的《CASSINI源代码分析》http://blog.csdn.net/shanhe/archive/2004/11/10/176422.aspx和 《实现自己的ASP.NET宿主系统》http://blog.csdn.net/shanhe/archive/2004/05/27/5429.aspx。

假设请求到了asp.net的框架,系统回怎么处理呢?

Asp.net系统框架自身有一个http处理的逻辑。这个逻辑由machine.config的 名为httpRuntime的节指定

     <httpHandlers>。。。</httpHandlers>

告诉asp.net框架一个应用程序的http处理程序缺省情况下该如何决定处理逻辑。所谓如何处理是指http 谓词(Verb)和资源的位置(Path)组合决定的请求的处理。

例如:

   <add

                verb="*"

                path="*.aspx"

                type="System.Web.UI.PageHandlerFactory" />

告诉asp.net在处理任何”.aspx”的文件的任何请求(post/get/head/put)时候都应该使用System.Web.UI.PageHandlerFactory类处理请求。这个类是系统框架缺省类,遵循System.Web.IhttpHandler接口(但是由MS官方实现)。如果你在研究透aspx页框架后,你可以自己写一个aspx页工厂处理程序,签名后加入全局应用程序,再修改此处即可让aspx页面没处理时是按照自己实现的逻辑进行(你甚至可以不遵循页面控件实现逻辑,虽然那样并无太大意义)。

 

我们来做个实验:我们利用浏览器发出对.config文件的请求(譬如web.config),却省情况下asp.net会报告错误,但是假若我们删除machine.config中的

  <add

                verb="*"

                path="*.config"

                type="System.Web.HttpForbiddenHandler" />

并保存。重新请求该文件后,那么会看到浏览器可以读取到此文件并可显示。重新加上该节,重新请求会看到系统报告错误。这充分说明了http处理工厂是根据配置文件进行处理的。据此类推,我们可以阅读machine.config的其他相关配直节,充分理解系统缺省处理逻辑是如何处理web请求(具体为合法的http请求)的。另外,web.config也可以据此增加/删除指定http处理程序。参考MSDN,我们发现,任何一个HTTP处理程序实际上是实现了System.Web.IhttpHandler接口的asp.net类。接口应当实现一个属性和一个借口方法:

公共属性

IsReusable 获取一个值,该值指示其他请求是否可以使用 IHttpHandler 实例。决定了此处理程序是否可以重用(通常克服用意味着系统性能的提高)

公共方法

ProcessRequest 通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。也就是处理实现。

 

假设我们写好了一个http处理程序,如何让他发挥作用呢?譬如,我们需要禁止asp.net下载.info文件,我们应该进行如下处理:

第一步,在web服务器上增加指示,让asp.net处理此扩展名。具体为:

打开IIS(假设web服务器是IIS),找到应用程序,配置,应用程序映射,添加映射,在可执行文件中输入当前.net版本的aspnet_isapi.dll(如C:/WINNT/Microsoft.NET/Framework/v1.0.3705/aspnet_isapi.dll),扩展名输入.info,谓词全部,检查文件是否存在。经过这些步骤,确保IIS不会自作多情处理.info文件,而让给aspnet_isapi.dll处理,后者则会将请求交给asp.net进行处理。

第二步,在machine.config或者web.config中加入相映配置节。都是在<System.Web〉中的加入<httphandler>子节,按照格式指定,譬如:

<httphandlers>

 …

         <add verb=”*” path=”*.info” type=” System.Web.HttpForbiddenHandler”>



</httphandlers>

经过上述节配置,.info文件会被保护起来,任何企图访问都会被告知“无法提供此类型的页。”;如果换成你自己的类,则被asp.net框架所调用来处理.info请求。

 

下面来实现一个http处理程序例子,我们增加一个扩展名.img,img文件根据参数生成一个图片,譬如我们向asp.net框架请求  abc.img,那么系统返回content为image/jpg格式的图片,图片内容为abc

源代码如下:

using System;

using System.Web ;

using System.Drawing; 

using System.Drawing.Drawing2D ; 

using System.Drawing.Imaging ; 

 

namespace ImyWeb

{

    public class IMG : System.Web.IHttpHandler  

    {

         public IMG()

         {}

 

           public bool IsReusable

        {

             get{ return true;}

    }

 

             public void ProcessRequest(HttpContext context)

        {

             string vstr=_getViewString(context);

             context.Response.ContentType ="image/jpeg"; 

             Image  img=new Bitmap(128,128,PixelFormat.Format32bppArgb );

             Graphics g=Graphics.FromImage(img);

         Brush backBrush=new SolidBrush(Color.Gray ); //灰色

                   Brush textBrush=new SolidBrush(Color.Black  ); 

             g.FillRectangle(backBrush,0,0,128,128);

                   Font ft=new Font( "Arial",32);

                   g.DrawString(vstr,ft,textBrush,new  RectangleF(0,0,128,128),new StringFormat(StringFormatFlags.NoWrap ));

                   img.Save(context.Response.OutputStream,ImageFormat.Jpeg );

                   context.Response .Flush();

         backBrush.Dispose();

textBrush.Dispose(); 

g.Dispose();

img.Dispose(); 

return ;

         }

 

          //************//

          private string _getViewString(HttpContext context)

         {

              string str= context.Request.RawUrl ;

              int l1=str.LastIndexOf("/");

              int l2=str.LastIndexOf("."); 

              return str.Substring(l1+1,l2-l1-1); 

         }

      }

}

编译后,得到应用程序为myHttpHandler.dll。

现在,我们需要告诉asp.net应用程序(假设我们的web应用程序在localHost/webApp1下),如何处理.img文件了:

第一步,在web服务器上增加指示,让asp.net处理此.img的扩展名(让IIS歇着吧,不要插手asp.net的事了,让asp.net处理*.img了)

第二,在web.config中的<system.web>节中增加:

<httpHndlers>

                   <add verb="*" path="*.img" type="IBuySpy.IMG,IMGHttphandler" />

</httpHndlers>

为了能够让asp.net程序能够定位到应用程序集,将myHttpHandler.dll拷贝到web应用程序的bin目录

接下来,我们测试:在测试的web应用程序WebApp1下随意请求一个.img的资源,将会得到一个jpg图片。http://localhost/WebApp1/test.img

 

以上为一个简单的httpHandler处理程序的设计、安装过程。实际上,假设你对asp.net缺省的*。aspx 的HttpHandler处理不满,你可以自己进行设计,替换覆盖掉machine.config中关于*.aspx的缺省处理类 "System.Web.UI.PageHandlerFactory”。你要明白,ms设计了一个服务框架,而这个框架内具有了基本的处理能力,但是优秀的是这个框架的部分落及是可以根据自己的意愿进行替换的(我们看到大量的接口使用,我们应当意识到那是使用了大量的现代软件设计技术和思想)。学习asp.net技术要站在全局来了解框架各个组成部分间如何执行、调用、发生关系、县户作用,这比实现了一个花哨的datagrid要更有意义。 
 

原创粉丝点击