转自:http://www.cnblogs.com/SkySoot/archive/2012/11/22/2782916.html
每一种自定义控件都有它的优缺点。以前曾介绍过用户控件。用户控件比自定义服务器控件更容易创建,但服务器控件的功能更强大。
服务器控件在两个方面比用户控件强得多:
- 服务器控件允许完全控制所生成的 HTML
- 服务器控件提供更好的设计时支持
所有的 ASP.NET Web 控件都是服务器控件。现在,你将学会如何创建自己的服务器控件。
自定义服务器控件入门
服务器控件是指那些直接或间接从 System.Web.UI.Control 类派生出来的 .NET 类。Control 类提供对所有的服务器控件都通用的属性和方法(例如 ID,ViewState 和 Controls 集合等)。
很多控件不能直接从 Control 类派生出来,它们是从 System.Web.UI.WebControls.WebControl 类派生出来的。这个类添加了一些功能来帮助你实现标准的样式,包括诸如 Font、ForeColor、BackColor 之类的属性。
为了更好的理解自定义控件是如何工作的,下面会给出几个简单的自定义控件的例子。
创建简单的自定义控件
为了创建一个基本的自定义控件,你的类需要从 Control 类派生并覆盖 Render()方法。Render()接收一个 HtmlTextWriter 对象,用来为控件生成 HTML。
生成 HTML 代码最简单的方法就是使用 HtmlTextWriter.Write()方法向页面写一个原始的 HTML 字符串。显然,不能用 Write()方法输出 ASP.NET 标签或其他服务器端内容,因为在把最终页面发送到客户端之前要为它呈现内容。
public class LinkControl : Control
{
// 继承 Control 类,重写 Render 方法输出一个 A 标签
protected override void Render(HtmlTextWriter writer)
{
writer.Write("<a href=‘http://www.apress.com‘>Click to visit Apress</a>");
}
}
HtmlTextWriter 类不仅让你写原始的 HTML,还提供了一些很有帮助的方法来帮你管理样式特性和标签。下一个例子给出了同一个控件,但略有一些差别。首先它使用 RenderBeginTag()和 RenderEndTag()将锚标签的开始和结束标签的呈现分开;其次,它增加了配置这个控件如何显示的样式特性:
public class LinkControl : Control
{
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Href, "http://www.apress.com");
writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "20");
writer.AddStyleAttribute(HtmlTextWriterStyle.Color, "Blue");
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.Write("Click to visit Apress");
writer.RenderEndTag();
}
}
你应该注意到在这个例子里的一些要点。首先,为了简化,例子中使用了几个枚举,这些枚举有助于避免因为排版的细微错误而导致的意外问题。
- HtmlTextWriterTag :这个枚举定义了许多 HTML 标签,如 <a>、<p>、<font> 等。
- HtmlTextWriterAttribute :这个枚举定义了许多公共的 HTML 标签特性,如 onClick、href、align 、alt 等。
- HtmlTextWriterStyle :这个枚举定义了 14 个样式特性,包括 BackgroundColor、BackgroundImage、BorderColor、BorderStyle、BorderWidth、Color、FontFamily、FontSize、FontStyle、FontWeight、Height、Width。所有这些零散的信息以分号分割的形式连接,形成 CSS 样式信息列表,用来呈现标签的样式特性。
当 Render()执行的时候,它首先定义所有将要添加到后续标签上的特性;然后创建开始标签;所有这些特性都被置于这个标签内部。最终呈现的标签如下:
<a href=”http://www.apress.com” style=”font-size:20;color:Blue;”>Click to visit Apress</a>
HtmlTextWriter 类的关键方法
AddAttribute() | 添加任意的 HTML 特性及其值到一个 HtmlTextWriter 的输出流。这个特性自动被应用到通过调用 RenderBeginTag()创建的下一个标签。(有枚举值可选) |
AddStyleAttribute | 添加任意的 HTML 样式特性及其值到一个 HtmlTextWriter 的输出流。这个特性自动被应用到通过调用 RenderBeginTag()创建的下一个标签。(有枚举值可选) |
RenderBeginTag() | 输出 HTML 元素的开始标签。(有枚举值可选) |
RenderEndTag() | 输出当前 HTML 元素的结束标签,不必指定标签名称 |
WriteBeginTag() | 与 RenderBeginTag()相似,但不输出开始标签的结束字符 >。这意味着可以调用 WriteAttribute()给标签添加更多的特性。要关闭开始标签,可以调用 Write(HtmlTextWriter.TagRightChar) |
WriteAttribute() | 输出一个特性到输出流,必须跟在 WriteBeginTag() 之后调用 |
WriteEndTag() | 给当前的 HTML 元素输出一个元素的结束符号 |
使用自定义控件
要使用自定义控件,你需要让它在 Web 应用程序中可用。有两种方法:
- 把源代码复制到 App_Code 目录下
- 把编译后的程序集添加到 Bin 目录下(添加引用)
对于那些能使用自定义控件的页面,必须使用 Register 指令(相似于用户控件),不仅必须包括一个 TagPrefix 标签,还要指定程序集文件,以及控件类所在的命名空间。你不需要指定 TagName,因为服务器控件的类名自动被使用。
<%@ Register TagPrefix="apress" Assembly="UserDesignControl" Namespace="UserDesignControl" %>
如果控件位于当前 Web 程序的 App_Code 目录下,就不需要 Assembly 特性。
<%@ Register TagPrefix="apress" Namespace="UserDesignControl" %>
你可以重用标签前缀,换言之,把两个不同的命名空间或者两个完全不同的程序集映射到同一个标签前缀完全有效。
如果想要在同一个 Web 应用程序的多个页面上使用同一个控件,可以用如下的方式配置 web.config 文件:
<configuration>
<system.web>
<pages>
<controls>
<add tagPrefix="apress" namespace="UserDesignControl" assembly="UserDesignControl"/>
</controls>
</pages>
...
</system.web>
</configuration>
当你从 VS 工具箱中拖曳一个控件的时候,VS 会选择一个默认的前缀。但配置了 web.config 后,就可以将你的控件前缀标准化。
工具箱中的自定义控件
为了便于使用,你也许会想要让这些自定义控件出现在工具箱里。如果你在单独的程序集里创建自定义控件的话,VS 的工具箱内置了对自定义控件的支持。
一旦创建了项目,就能定义控件了。开发控件库项目的方式与开发其他 DLL 组件一样。可以随时构建项目,但是不能直接启动它,因为它不是一个实际的应用程序。为了测试控件,需要在另一个应用程序中使用它们。
VS 每次编译项目时,被引用的程序集的最新版本将被复制到 Web 应用程序的 Bin 目录下,也就是说,不用担心更新过被引用的控件版本。
自动工具箱(在设计选项卡时)会自动侦测 Web 程序中包含的自定义控件,并将它添加到一个临时的项目专用的区域:
如果你想要控件在任何 Web 程序中都可用,但又不想让 Web 应用程序开发人员更改你的自定义控件的代码,在这种情况下,你只需部署编译后的程序集。然后,你可以永久性的添加这些控件到工具箱中(选项卡查找程序集)。
创建支持样式属性的 Web 控件
前一个自定义的例子不允许网页定制控件的外观。LinkControl 控件没有提供任何属性来设置所生成的 HTML 标签的前景色、背景色、字体及其他特性。换句话说,LinkControl 控件不允许外部代码改变它生成的 HTML。为了更灵活,需要显式添加代表这些属性值的公共属性,然后需要在 Render()中读取这些属性并生成相应的 HTML 代码。
样式属性是许多 HTML 控件用到的基础设施的基础部分。比较理想的是,所有的控件应该遵循同一个有效的样式信息的简化模型,并且不强迫自定义控件的开发者自己编写这个通用的功能。ASP.NET 通过 WebControl 基类实现了这点(System。Web.UI.Controls 命名空间中)。ASP.NET 中的每个控件都从 WebControl 类派生出来,因此,你也可以从 WebControl 类中派生出自定义控件。
WebControl 类不仅包含基本的样式相关属性,如 Font、ForeColor、BackColor 等,它还自动的把这些属性呈现在控件标签里。这里给出其工作原理:WebControl 假设它应该添加这些特性到一个 HTML 标签,称作基本标签(base tag);如果你在输出多个元素,这些特性被添加到包含其他元素的最外围元素。你应在构造函数里为 Web 控件指定基本标签。
最终,你不用覆盖 Render()方法,WebControl 类已经包含了 Render()的实现,这个实现由以下3个方法构成:
- RenderBeginTag():输出控件的开始标签以及你指定的特性
- RenderContents():输出开始标签和结束标签中间的所有内容,包括文本内容和其他 HTML 标签。这个方法是经常覆盖来输出自定义控件内容的方法
- RenderEndTag():输出控件的结束标签
当然,如果需要也可以通过覆盖 Render()方法来改变这一行为。但是,如果这个基本框架适合你的需要,你只需写很少的自定义代码就能实现所需功能。
下一例子显示了从 WebControl 派生出的一个新的链接控件,由此获得了样式属性的自动支持:
public class LinkWebControl : WebControl
{
// WebControl 有多个版本的构造函数,其中有允许你传入标签的构造函数
// 这里传递的基本控件标签是 锚标记 <a>
public LinkWebControl()
: base(HtmlTextWriterTag.A)
{
// 构造函数不需要任何的代码.
// 唯一重要的是,利用这个机会调用 WebControl 构造函数来设置基本控件标签
}
// 定义两个属性,允许网页设置 文本 和 URL
public string Text { get; set; }
private string hyperLink;
public string HyperLink
{
get { return hyperLink; }
set
{
if (value.IndexOf("http://") == -1)
{
throw new ApplicationException("Specify HTTP as the protocol");
}
else
{
hyperLink = value;
}
}
}
// 在定义文本和超链接变量的时候,你可以将其设置为空字符串
// 这里故意覆盖了 OnInit() 方法来显示怎么通过编程初始化一个控件
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// if no value were set in the control tag, apply the defaults now.
if (hyperLink == null)
hyperLink = "http://www.google.com";
if (Text == null)
Text = "Click to search";
}
// WebControl 类可以覆盖下列方法,因此能添加额外标签
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Href, this.HyperLink);
base.AddAttributesToRender(writer);
}
// 添加文本
protected override void RenderContents(HtmlTextWriter writer)
{
writer.Write(this.Text);
base.RenderContents(writer);
}
}
注意,无论何时自定义控件覆盖一个方法,它都应该通过 base 调用基类的实现。这样做确保你不会意外的抑制住其他需要运行的代码。通常,基类方法所做的就是激发一个相关事件。例如,如果你覆盖了 RenderBeginTag()方法,而且又没有调用基类实现,呈现代码将会失败并抛出一个未处理的异常,因为这个标签不是打开的。
Visual Studio 中的自定义服务器控件
手工创建的控件和 Visual Studio 生成的控件有一个重要的区别。由 VS 生成的控件包含了一些自动生成的样板代码:
- 文件的开始有一组 using 语句,它们导入有用的 ASP.NET 命名空间
- 控件类添加了 Text 属性,它被保存到视图状态里
- 控件类覆盖了 RenderContents()以输出 Text 属性的内容
- 控件类声明和 Text 属性被配置了设计时支持的特性修饰(如 DefaultProperty 表示选中控件时,突出显示的属性)
没有 VS 的帮助,增加这些细节也非常容易。因此不必害怕空代码文件以及手工编写自定义控件类。
呈现过程
前面的例子介绍了几个新的呈现方法,现在让我们看看它们是如何协同工作的。
呈现过程的起点是 RenderControl()方法。RenderControl()是 ASP.NET 将每个控件呈现成 HTML 的公共呈现方法,你不能覆盖它。RenderControl()调用启动呈现过程的 Render()方法,你可以覆盖这个方法,如第一个例子所示。然而,如果你覆盖了 Render()方法,而没有调用 Render()方法的基类实现,其他的呈现方法都不会被激发。
Render()方法的基类实现调用了 RenderBeginTag()、RenderContents()、RenderEndTag()方法,如前例所示。这里还有一个小插曲,RenderContents()方法的基类实现调用了另一个呈现方法 RenderChildren()。这个方法遍历了 Controls 集合中的子控件集合,并调用了每个子控件的 RenderControl()方法。通过这种方式,你可以很容易的基于其他控件构建自己的控件。在后续的组合控件文章中我会介绍。
那么,应该覆盖哪一个呈现方法呢?
- 如果想用新的内容替换整个呈现过程,或者想在基本控件标签之前添加 HTML 内容(如 Javascript 代码块),你可以覆盖 Render()方法。
- 如果想利用自动的样式特性,你应该定义一个基本标签(调用基类构造函数并指定标签名参数),并覆盖 RenderContents()。
- 如果想阻止子控件被显示或者要定制它们的呈现方式(例如,让她们以相反的顺序呈现),你可以覆盖 RenderChildren()。
值得注意的是,你可以调用 RenderControl()方法来检查一个控件的 HTML 输出,这个技术是一个调试时的便捷方式:
StringWriter writer = new StringWriter();
HtmlTextWriter output = new HtmlTextWriter(writer);
LinkWebControl1.RenderControl(output);
Label1.InnerHtml = "The HTML for LinkWebControl1 is <br /><blockquote>"
+ Server.HtmlEncode(writer.ToString()) + "<blockquote/>";
这个方法不仅仅是用于调试的,也可以用来简化你的呈现代码。例如,你也许发现创建和配置一个 HtmlTable 控件,然后调用它的 RenderControl()方法是比较容易的,而不是直接输出 <table>、<tr>、<td> 标签到输出流。