- 第18章 自定义服务器控件
第18章 自定义服务器控件
对于自定义服务器控件,相信大家并不陌生,在前面的章节中也有过类似的自定义服务器控件示例。与一般ASP.NET标准服务器控件相比,自定义服务器控件完全由开发人员自行设计开发,开发人员可以自定义UI、功能、属性、方法、事件等特征;而与前面所讲的用户控件相比,虽然用户控件比自定义服务器控件更容易创建,但自定义服务器控件的功能更加强大。因此,自定义服务器控件具有以下特点:
❑灵活性强:开发人员可以根据应用需要,自定义其中的UI、功能、属性、方法和事件等。
❑样式支持:由于自定义服务器控件可能派生自System.Web.UI.WebControls,因此通过继承的Style属性可定义样式,例如字体、高度、宽度、颜色等。
❑提供对标准服务器控件的扩展功能:自定义服务器控件可在继承标准服务器控件的基础上,扩展或改进相关属性、方法、功能等,甚至可以将不同的服务器控件组合起来,形成复合控件。
❑可复用性高:当创建好一个自定义服务器控件之后,就可以在任意项目中使用,与使用标准服务器控件一样方便。
❑易于部署:具有“即插即用”的特征,开发人员只要将编译好的自定义服务器控件复制到相关的bin目录即可使用。
❑创建难度高:开发自定义服务器控件需要开发人员精通多方面技术,同时,还需要耗费大量的精力和时间。
18.1 创建简单的自定义服务器控件
简单地讲,常见的自定义服务器控件分为4类:复合控件、验证控件、模板控件和数据绑定控件。其中:
1)复合控件包含两个或两个以上已存在控件,它复用了子控件提供的实现来进行控件呈现、事件处理及其他功能。
2)验证控件与前面所述标准服务器控件中的验证控件定义相同。其实,早在4.8.2节中就对此类自定义服务器控件做过比较详细的阐述。
3)模板控件提供了一种称为模板的通用功能。模板控件本身不提供用户界面,而是通过内联模板提供,这意味着模板控件允许页面开发人员自定义该控件的用户界面。
4)数据绑定控件与上文所述标准服务器控件中的数据绑定控件定义相同。
下面就通过一个简单的示例来详细阐述一下如何开发与使用自己的自定义服务器控件。
18.1.1 创建MyLink控件
其实,要创建一个简单的自定义服务器控件,所要做的只是定义从System.Web.UI.Control派生的类并重写它的Render方法。Render方法采用System.Web.UI.HtmlTextWriter类型的参数,控件要发送到客户端的HTML作为字符串参数传递到HtmlTextWriter的Write方法。
其中,System.Web.UI.Control类定义由所有ASP.NET服务器控件共享的属性、方法和事件。同时,它也是开发自定义ASP.NET服务器控件时从中派生的主要类。但需要注意的是,该类没有任何特定于用户界面((Uer Interface, UI)的功能,如果要创建没有UI的控件或者组合其他呈现它们自己的UI的控件,则可以从该类进行派生。
下面的示例创建了一个简单自定义服务器控件MyLink。该控件功能很简单,就是用Render方法中的HtmlTextWriter来生成一个超链接。如代码清单18-1所示。
代码清单18-1 MyLink.cs
using System;
using System.Web.UI;
namespace_18_1
{
public class MyLink:Control
{
public override void Render(HtmlTextWriter writer)
{
writer.Write("<a href=\"
http://www.comesns.com/aspnet\">
ASP.NET4程序设计</a>");
}
}
}
创建好MyLink控件之后,就可以在页面使用该控件了。引用自定义服务器控件的方法详见第4.8.3节。完整的页面代码如下所示:
<%@Page Language="C#"AutoEventWireup="true"
CodeBehind="WebForm1.aspx.cs"Inherits="_18_1.WebForm1"%>
<%@Register Assembly="18-1"Namespace="_18_1"TagPrefix="cc1"%>
<!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>
<cc1:MyLink ID="MyLink1"runat="server">
</cc1:MyLink>
</div>
</form>
</body>
</html>
到现在为止,一个简单的MyLink控件的创建与使用的例子就全部完成了。运行页面,就可以看到示例运行结果,如图18-1所示。
图 18-1 示例运行结果
18.1.2 创建支持样式属性的MyLink控件
前面已经说过,使用System.Web.UI.Control类创建的自定义服务器控件没有任何特定于用户界面的功能。如果自定义服务器控件要呈现用户界面元素或任何其他客户端可见的元素,则应从System.Web.UI.WebControls的WebControl(或派生类)派生该控件。其中,WebControl类从Control派生,并添加了与样式相关的属性,如Font、ForeColor和BackColor等。此外,一个从WebControl派生的控件也自行参与到ASP.NET的主题功能。
下面的示例使用了派生WebControl类来创建了MyLink控件,这样使MyLink控件支持样式属性设置,如代码清单18-2所示。
代码清单18-2 MyLink.cs
using System;
using System.Web.UI;
namespace_18_1
{
public class MyLink:System.Web.UI.WebControls.WebControl
{
public MyLink():base(HtmlTextWriterTag.A)
{
}
private string_text;
public string Text
{
set
{
_text=value;
}
get
{
return_text;
}
}
private string_url;
public string Url
{
set
{
if(value.IndexOf("http://")==-1)
{
throw new ApplicationException("Url中没写
http://");
}
else
{
_url=value;
}
}
get
{
return_url;
}
}
protected override void AddAttributesToRender(
HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Href,
_url);
base.AddAttributesToRender(writer);
}
protected override void RenderContents(HtmlTextWriter
writer)
{
writer.Write(Text);
base.RenderContents(writer);
}
}
}
对于代码清单18-2中的MyLink控件,这里还需要做如下几点说明:
1)MyLink类默认的构造函数调用了WebControl的构造函数。其中,WebControl的构造函数有多个,这里调用的构造函数允许你指定一个基本控件标签—锚<a>。如下面的代码所示:
public MyLink():base(HtmlTextWriterTag.A)
{
}
2)MyLink类中的Text与Url属性分别用于设置链接文本与链接URL。
3)需要特别注意的是,这里的AddAttributesToRender方法用于将需要呈现的HTML特性和样式添加到指定的HtmlTextWriterTag中(即用于添加由控件呈现的开始标记中的href特性),而该方法的HtmlTextWriter参数表示要在客户端呈现HTML内容的输出流。
其实,如果需要在客户端为自定义Web服务器控件呈现特性和样式,可以调用AddAttribute和AddStyleAttribute方法将每个特性和样式分别插入到HtmlTextWriter输出流中。为了简化该过程,这里的AddAttributesToRender方法为与Web服务器控件关联的每个特性和样式封装所有对AddAttribute和AddStyleAttribute方法的调用。在单个方法调用中,将所有特性和样式插入到HtmlTextWriter输出流中。
一般情况下,AddAttributesToRender方法通常由控件开发人员在派生类中重写,以将适当的特性和样式插入到类的HtmlTextWriter输出流中。如下面的代码所示:
protected override void AddAttributesToRender(
HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Href,_url);
base.AddAttributesToRender(writer);
}
这里还需要特别说明一点的是,无论何时在自定义控件中覆盖一个方法,它都应该通过使用base这个关键字来调用基类的实现。这样做可以确保你不会意外地抑制住其他需要运行的代码。
与利用System.Web.UI.Control派生的类创建控件一样,做好上面这些处理之后,就可以通过覆盖WebControl类的RenderContents方法将控件的内容呈现到指定的编写器中。如下面的代码所示:
protected override void RenderContents(HtmlTextWriter writer)
{
writer.Write(Text);
base.RenderContents(writer);
}
到现在为止,MyLink控件就创建完成了,现在就可以在页面中来使用它。在使用中,除了可以设置它的Text与Url属性之外,还可以设置它的样式属性。如下面的代码所示:
<%@Page Language="C#"AutoEventWireup="true"
CodeBehind="WebForm1.aspx.cs"Inherits="_18_1.WebForm1"%>
<%@Register Assembly="18-1"Namespace="_18_1"TagPrefix="cc1"%>
<!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>
<cc1:MyLink ID="MyLink1"BackColor="Blue"
Font-Bold="true"Font-Size="X-Large"
ForeColor="Yellow"
Text="ASP.NET4程序设计"
Url="http://www.comesns.com/aspnet"runat="server"/>
<br/>
<br/>
<cc1:MyLink ID="MyLink2"ForeColor="Blue"
Font-Bold="true"Font-Size="X-Large"Text="易学C#"
Url="http://www.comesns.com/csharp"runat="server"/>
</div>
</form>
</body>
</html>
示例运行结果如图18-2所示。
图 18-2 示例运行结果
18.1.3 通过派生现有的控件来创建MyLink控件
其实,除了上面两种自定义服务器控件的创建方法之外,还可以选择使用从现有的控件类中派生出一个更加专用的控件来创建自定义服务器控件。在这种方法中,可以通过覆盖或者直接增加所需要的功能,而无须去重新创建整个控件。
例如,下面的MyHLink控件就从System.Web.UI.WebControls.HyperLink控件中派生而来,并重写了HyperLink的OnInit方法,使该控件在Text和NavigateUrl属性值为空时,可以默认一个值。如代码清单18-3所示。
代码清单18-3 MyHLink.cs
using System;
using System.Web.UI;
namespace_18_1
{
public class MyHLink:System.Web.UI.WebControls.HyperLink
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if(this.Text==null||this.Text=="")
{
this.Text="ASP.NET4程序设计";
}
if(this.NavigateUrl==null||this.NavigateUrl=="")
{
this.NavigateUrl="http://www.comesns.com/aspnet";
}
}
protected override void RenderContents(HtmlTextWriter
writer)
{
base.RenderContents(writer);
}
}
}
下面分别在页面创建两个MyHLink控件。其中,给MyHLink1中的Text和NavigateUrl属性进行赋值;而给MyHLink2中的Text和NavigateUrl属性不进行赋值,使它采用控件的默认值。如下面的代码所示:
<%@Page Language="C#"AutoEventWireup="true"
CodeBehind="WebForm1.aspx.cs"Inherits="_18_1.WebForm1"%>
<%@Register Assembly="18-1"Namespace="_18_1"TagPrefix="cc1"%>
<!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>
<cc1:MyHLink ID="MyHLink1"runat="server"
Font-Size="X-Large"Text="易学C#"
NavigateUrl="http://www.comesns.com/csharp">
</cc1:MyHLink>
<br/>
<br/>
<cc1:MyHLink ID="MyHLink2"runat="server"
Font-Size="X-Large">
</cc1:MyHLink>
</div>
</form>
</body>
</html>
示例运行结果如图18-3所示。
18.1.4 呈现过程
图18-4展示了呈现过程。
图 18-3 示例运行结果
图 18-4 呈现控件的方法
简单地讲,呈现过程的起点是RenderControl方法,该方法是ASP.NET用来把网页上的每个控件呈现成HTML的公共呈现方法,不能够像“protected override void RenderControl(HtmlT extWriter writer)”这样来覆盖该方法。
然而,RenderControl方法却调用了启动呈现过程的受保护的Render方法,可以覆盖Render方法进行呈现。但是,如果覆盖了Render方法,而没有调用Render方法的基类实现,则其他的呈现方法都不会激发。
Render方法的基本实现调用了RenderBeginTag、RenderContents与RenderEndTag方法。而RenderContents方法的基本实现却调用了RenderChildren方法。
其中,RenderChildren方法用于将服务器控件子级的内容输出到提供的HtmlTextWriter对象,此对象编写将在客户端呈现的内容。该方法遍历了Controls集合中的子控件集合,并且调用了每个控件的RenderControl方法。通过这种方式,可以很容易地从其他控件构建自己的控件。
既然这么多呈现方法,应该怎么选用呢?这里给出如下几点建议:
1)如果想用新的内容替换整个呈现过程,又或者想在基本控件标签之前添加HTML内容(如JavaScript代码块),那么可以采用覆盖Render方法。
2)如果想利用自动的样式特性,则应该定义一个基本标签,并覆盖RenderContents方法。
3)如果想阻止子控件被显示或者要定制它们的呈现方式,则可以覆盖RenderChildren方法。