.net Customer Server Controls——客户化服务端控件

用户控件容易创建,但它仍有局限,如:不可像常规的控件那样加入到ToolBox,不能在属性窗体中处理事件。客户化服务端控件较用户控件更复杂,更难实现,但也更强大,更灵活。

*
有两种方式来创建服务端控件。最简单的方法是:创建DLL类库。另一方式是创建Web控件类库。后一种方式一般是我们首选的方法,但它涉及到很多的代码。先来看第一种方式:
*

**
创建新项目,项目类型选择类库。注意路径,最好同网站项目文件夹同一父目录(为了引用方便)。
因为该DLL文件将包含Web类相关的代码,所以先添加到System.Web.dll的引用。
右击“引用”文件夹图标——》添加引用(R)。之后选择System.Web组件。

在添加了引用之后,还要在类定义文件中导入必要的名空间。至少需要两个:
using System.Web;
using System.Web.UI;

————
前一个名空间含有我们的自定义类(UDT)需要引用的类。
后一个名字空间,定义了如:Control,HtmlTextWriter类,我们用此类产生HTML代码。
以下是简单的示例:
using System;
using System.Collections.Generic;
using System.Text;

using System.Web;
using System.Web.UI;

namespace UDTServerControl1
{
    public class MyBlogLinkControl : Control
    {
        protected override void Render(HtmlTextWriter writer)
        {
            writer.Write("<a href='http://hi.baidu.com/yiqing95/'>我的空间</a>");
            //该控件的外观由Render方法渲染而出。
        }

    }
}
—————————
该类很简单,主要覆写了Render方法。该方法会将控件的外观渲染为HTML元素。
接下来,你可生成解决方案。点击解决方案资源管理器窗体的“显示所有文件”图标,在bin子目录下的Debug目录下可看到UDTServerControl1.dll和UDTServerControl1.pdb文件。其中的dll文件就是我们生成的程序集,可被用于其他Web项目。
**

***
使用我们的自定义服务端控件:

1.在你的网站项目中,添加引用到含有自定义服务端控件类定义的DLL文件。
2.注册自定义服务端控件,使用Register指令如:
<%@ Register Assembly ="UDTServerControl1" Namespace ="UDTServerControl1"     TagPrefix ="udt" %>
———解释如:——程序集的名字为UDTServerControl1。我们对该程序集中的UDTServerControl1名字空间下的类感兴趣,该空间下的控件所对应的标签tag将以前缀udt引领。      
我们的程序集名字正好与唯一的一个名空间同名,其实一个程序集中可有多个名字空间。最终使用代码如:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly ="UDTServerControl1" Namespace ="UDTServerControl1" TagPrefix ="udt" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>无标题页</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
       <udt:MyBlogLinkControl ID="MyBlogAddress" runat         ="server"></udt:MyBlogLinkControl>
   
    </div>
    </form>
</body>
</html>

***

****
以上给出的自定义服务端控件类定义在实际应用中其实可能很复杂。服务端控件类最重要的就是那个Render方法。它可能很复杂。其实你只要抓住核心思想,也没什么可惧怕的。

该方法其实就是把控件(或其内部数据,本例没有内部数据)以HTML元素的形式展示出来。我们在覆写该方法时,就是想法构造HTML语言的片段。当然你可使用HtmlTextWrite的write方法写出任意的HTML片段。但是还是有另外一种比较好的构造手法:
如 我们要构造这样的片段:

<p bordercolor="Blue" style="color:#33ccff;"><marquee>
    该段内容会滚动!
</marquee></p>

Render方法中可以这样书写:
protected override void Render(HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Bordercolor, "Blue");
            writer.AddStyleAttribute(HtmlTextWriterStyle.Color, "#33ccff");

            writer.RenderBeginTag(HtmlTextWriterTag.P);
            writer.RenderBeginTag(HtmlTextWriterTag.Marquee);

            writer.Write("该段内容会滚动!");

            writer.RenderEndTag();
            writer.RenderEndTag();//注意与XXXBeginXXX配对
         }
其实没什么,就是换了一种书写html代码的方式而已!!但你还是要学会转换思路 :-) 。

Html中的Tag名字这里被定义为枚举类型,这样你可使用智能感知,减少记忆负担和出错的可能。而其中的属性也是枚举类型,如上面出现的HtmlTextWriterAttribute.Bordercolor,和HtmlTextWriterStyle.Color。一个是常规属性,另一个是样式表CSS的属性。也是比较方便的,就是不太直观而已。

注意AddXXXAttribute方法出现的位置,表示这些属性是属于那个开始标记的。原则:向下最近匹配 RenderBeginTag方法参数中的那个tag。这里匹配的是<P>。

随便找一个网页,看一下它的源代码,你可以发现其中 无非就是:元素开始标记 ,其他元素或文本,元素结束标记。每个元素有名字,有一些属性。就这些东西,用HtmlTextWriter类的方法表示出来就是了。行了就啰嗦到这里,自己琢磨吧 :-)
****

*****
我们以上在使用自定义服务端控件有时显得很麻烦,(需要手动添加引用,手动注册,还要手动写tag....),既然它也是控件,可不可以像其他常规控件那样从工具箱中拖拽使用呢??答案是肯定的:-)

看看我们如何把自定义的控件类加到工具箱中(我这里用的是VS2005中文简体版):
在工具箱最下方空白处右击——》选择项(I)——》弹出一个“选择工具箱项”对话框,点击“浏览”按钮,在“打开”对话框中选择你的那个包含自定义服务端控件的类定义的程序集文件(.dll/.exe)。之后会看到一个齿轮状的图标新添加进了工具箱,它就是你的自定义服务端控件类,接下来像常规控件那样使用就行了OK。

当你从工具箱中拖拽了代表自定义服务端控件类的图标到Web窗体的设计视图或"源"视图后,看一下源 代码。里面自动出现了注册行如:<%@ Register Assembly="UDTServerControl1" Namespace="UDTServerControl1" TagPrefix="cc1" %>
*****
******
目前自定义的控件,仅含有少量继承自Control类的属性,方法和事件。根据需要自己添加属性,方法和事件。同一般类的设计一样,这里不在敷述。之后,公共的属性和事件都会出现在自定义服务端控件的属性窗体中。或者当在"源"码文件中为代表控件的标记Tag元素设置属性时智能感知列表中会出现自定义的属性和事件项。这些归功于反射。
******

*******
上面的设计方法,我们看到,为了访问控件内部的东西,我们不得不手写许多为了访问内部数据的属性,方法或是事件。试想一下这样的情形:一个整体包含许多部分,由于封装的原因,我们想访问内部的部分,不得不在整体的边界处对外部提供到访问内部组件的路由(如属性,方法,索引器等)。这会是一个很费时的工作。微软意识到这样的事实,所以提供了比System.Web.UI.Control类更智能的类——WebControl。
*******
********
WebControl类封装了设定样式的能力,提供了高级的创建自定义服务端控件的基础。下面看看我们的UDT服务端控件如何使用WebControl类库。

1.新建一项目,模板选择“Web 控件库”。名称自拟。在项目的“引用”文件夹下可以看到 System.Web程序集默认导入。
看看我们的类代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;   
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyWebControls
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")]
    public class WebCustomControl1 : WebControl
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text
        {
            get
            {
                String s = (String)ViewState["Text"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Text"] = value;
            }
        }

        protected override void RenderContents(HtmlTextWriter output)
        {
            output.Write(Text);
        }
    }
}————————————————————
以上是VS2005为我们生成的代码,类名可以更改,但最好把所有出现类名如WebCustomControl1的地方都更改为你的自定义类名。看到,该类框架中仅有一个属性Text和一个覆写的方法RenderContents(该方法的用途跟Control类的Render类似)该方法负责输出控件开始标记和结束标记之间的内容(一般是纯文本,但也可以是HTML片段),并不负责输出开始标记或结束标记。

注意到那些中括号中的东西,它们称特性Attribute(特性)。特性也是类型,中括号中的东西表示一个类型的实例,你看它们是不是很像一个方法??其实它们跟构造方法的作用相似,括号中的参数有的就是应用于构造方法,不过也可能是传递给属性成员。特性信息可应用元数据表项上,运行时Runtime通过查询这些可扩展的元数据信息来动态改变代码的执行方式,特性可以方便地表达开发人员的意图。【关于特性的详细介绍详见:Jeffrey Richter著的 《Applied Microsoft.NET Framework Programming》——此书被称为.net领域的圣经】

##所有的服务端控件都有起始标记和结束标记,二者之间可有Text文本信息。
请注意上面的Text属性的默认实现!!我们常规实现类的属性时一般是对某个私有字段的访问(读或写),而这里的实现并不似常规那样:内部有一个存储Text属性信息的成员。只所有这样实现的原因是这样的:Web窗体(含有若干Web控件)是无状态的(本地变量,短生命期),其显示的信息一般是“读”后台或自己的数据。当把信息“写”到Web控件实例时的私有字段时,第二次返回到浏览器渲染的信息还是同第一此请求(第一次产生的对象及其私有数据已被丢弃)一样。所以数据会保存在一个全局数据对象内(这里是ViewState对象,其它的全局数据对象还有Session,Application等),这些全局(长生命期)的对象访问方法是通过索引器Indexer来访问的,你可理解它们内部的数据容器是一个哈希表或是字典,它们通过键Key值来存取对象。这是一个较棘手的关于Web程序如何解决状态存储的话题,请参考相关文章。
2.使用自定义的 服务端WebControl。
同使用自定义的控件方法一样,先将含有该控件定义类的项目生产程序集:dll或exe文件。
将其加入到“工具箱”窗体中。之后出现代表该类的齿轮图标。接下来可在你的Web网站中像常规控件那样使用该控件。你可看一些该自定义Web控件的属性窗体多出了很多属性(比Control控件多多了)。当你运行Web程序,在浏览器端查看HTML源码时,可看出WebControl默认会被渲染为<span></span>元素。如果你想把自定义的WebControl控件渲染为指定的标记Tag,则需要在源代码中做些更改,添加一个构造方法如:
public WebCustomControl1() : base( HtmlTextWriterTag .Button )
        {
          
        }//————该构造方法指出自定义控件的起始标记和结束标记将是一个按钮
      
HtmlTextWriterTag是一个枚举类型,你可以根据该例将自定义服务端控件的标记改为其他标记。
********

*********
以上自定义Web控件类中,除了默认的Text属性外没有其他属性,如果你想添加自己的属性则可以仿照Text属性的定义方法创建,注意带上那些特性。注意那个Category属性,它表示在属性窗体中下面定义的属性出现在哪里(如:布局,可访问性,数据,外观,行为,杂项等),上面的是Appearance——表示属性将出现在“外观”段,你页可以将其改为:Navigation。

要注意的一点:在“源”视图(或属性窗体)中自定义Web控件的属性,并不等同于渲染成HTML页元素的属性。其实所有的ASPX源码中非标准HTML元素的属性并不一定对应HTML元素的属性,别被直觉所迷惑,它们会被解析,替换,执行或重组,最终才成为标准的浏览器可解析的HTML文件。所以如果我们想输出标准HTML元素的属性,还需要其他的工作:
添加 ---》》
        protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.XXX, this.SomerProperty);
            writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, "LightGreen");

            base.AddAttributesToRender(writer);
        }
//------------------注意调用writer的AddXXXAttribute方法添加欲设定的属性。最后一行的//base.AddAttributesToRender(writer);不要遗忘,它保证其他必要的属性被输出。

*********
**********
我们知道并不是所有的HTML元素都有Text属性,如<br/><img ... /><hr ..../>等标记并没有Text要输出,所以在自定义Web控件中碰到类似情形时删去RenderContents方法:
protected override void RenderContents(HtmlTextWriter output)
        {
            output.Write(Text);
        }//------------------------该方法可被删去,或空方法体。

##
对于输出HTML的话题,浏览器类型(或版本这里先不探讨版本问题)有时不得不考虑,因为当下主流浏览器对HTML标签的支持可能稍有不同(如Marquee,以前仅IE支持,现在其他浏览器可能也支持了),我们有时为了支持多浏览器显示,会对不同的浏览器输出不同的HTML元素。
在.net框架的安装目录中有关于各浏览器能力的文件,路径如:C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/CONFIG/Browsers该文件夹下的文件以.browser后缀结尾,如ie.browser其中声明了IE特定版本对某些能力的支持(如对JavaScript)。
这些文件帮助ASP.net在对不同的浏览器如何渲染Web控件起了很重要的作用。你可以看看它们。
你也可以根据浏览器类型手动输出不同的内容 ,如更改RenderContent方法:
    protected override void RenderContents(HtmlTextWriter output)
        {
           //每个WEB控件可访问Page对象,
           //如果将它们看成树,就是说所有的叶或枝节点都可访问根节点
           //Request对象封装浏览器送来的请求,这应是很熟悉了吧!!
            if (Page.Request.Browser.Browser == "IE"){
                output.Write(Text+" displayed in IE browser.");
            }
            else {
                output.Write(Text + " displayed in non_IE browser.");
            }
           
        }//以上代码先判断浏览器的类型,然后根据类型决策输出的内容,你可在不同的浏览器中看看输出。当然这里只是很简单的思路示例而已,实际中可任意复杂。
**********

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
最后的话题:
因为Web窗体毕竟不同与一般的Windows Forms窗体,由于网络通讯昂贵的原因,所以数据的传递一般的是批处理,一般仅在点击提交按钮时才将所有输入控件中的数据进行收集和封装(如URL编码)。
服务端控件如果打开AutoPostBack开关,则在某些事件下也是可以传递数据的。对于处理PostBack事件,自定义Web控件可以实现IPostBackDataHandler接口如:
   public class WebCustomControl1 : WebControl ,IPostBackDataHandler
    {
        public WebCustomControl1() : base( HtmlTextWriterTag.Input )
        {
          
        }
        //....其他成员定义,这里略
protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Type , "Text");
            writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, "LightGreen");
            base.AddAttributesToRender(writer);
        }

        #region IPostBackDataHandler 成员
       
        public bool LoadPostData(string postDataKey,                        System.Collections.Specialized.NameValueCollection postCollection)
        {
            //该方法传递从浏览器中送来的数据。
            string postBackedValue = postCollection[postDataKey];
            //处理 回送的数据..........
            对比以前的数据如果不同则返回 true,
            相同则返回 false。
            即 :如果该控件中数据较前一次有所变化则该方法返回true,否则返回false。
            而当该方法返回 true时回导致ASP.NET调用下面的RaisePostDataChangedEvent方法。
            但是你自己可以决定到底什么标准才算变化了。
        }

        public void RaisePostDataChangedEvent()
        {
           该方法在LoadPosteData返回true时被调用,
           你可在这里触发事件,或调用某些方法。
          (很明显用了模板方法设计模式)
           $$$$
             一般的做法是,定义一个公共事件,且定义一个虚方法
             ,但在该方法中调用虚方法,而在虚方法中调用事件。
              这样子类可以覆写该虚方法,进而让它们有其他的处理选择。
           $$$$
        }

        #endregion
    }
//--------------因为所有从网页上收集的数据都是打包过来的,我们在服务端访问时类似于访问HashTable或字典,所以有必要给出每个HTML元素一个Name属性,这就是访问时的键值。

####UDTServerControl可以定义事件,然后在Web项目的属性窗体中可视化注册事件的处理方法。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
       请仔细体会下面的代码,它们原本位于自定义Web控件的内部。
       以下的做法在.net框架中很常见。
       public event EventHandler SomeEvent;

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

        #region IPostBackDataHandler 成员
        
        public bool LoadPostData(string postDataKey,                        System.Collections.Specialized.NameValueCollection postCollection)
        {
            string postBackedValue = postCollection[postDataKey];
             据某些条件返回一布尔值
        }

        public void RaisePostDataChangedEvent()
        {
            OnSomeEvent(new EventArgs ());
        }

        #endregion

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值