NoSpamEmailHyperlink: 2.属性&呈现

分享于 

22分钟阅读

Web开发

  繁體

Transformation from ASP.NET properties to HTML

简介

这是一系列六篇文章中的第二个,后面是完整功能的ASP.NET 自定义控件的设计。开发和。

全文的完整 List 如下所示:

这些文章不是对自定义控制开发( 有 700页的网页书几乎没有覆盖它)的全面看法,但它们覆盖了很多基本的基础。

目的是在一个完全可以重用和可以自定义的控制( 与许多虚构的例子相反) 上进行这样的操作。

本文对属性和 ViewState 处理。不同属性 PersistenceMode 值和简单控件呈现功能进行了。 它是针对那些较新的编写自定义控件但已经学习了很多基本知识的人。

它至少具备 C#。WebControl -derived类和. NET 框架属性的基本知识。

注意:下载的第一篇文章中提供了下载。

属性句柄

NoSpamEmailHyperlink 基于 Hyperlink 控件的功能,但并非从 Hyperlink 派生,主要是因为 .Email 是我们用来构建超链接的属性的更精确的NAME。

它直接从 System.Web.UI.WebControls.WebControl 继承基类在格式化属性方面所提供的所有内容。

它还添加了五个 public 属性,允许页开发人员在控件级别自定义处理:

.Text文本将显示为超链接的内部文本。 默认为电子邮件地址。
.Email超链接应指向的地址。
.ScrambleSeed定义用于编码的整数种子。 默认为 23。
.EncodeInText.Text 中对它的存在的电子邮件地址进行编码。 默认为 true
.HideText用于隐藏浏览器无法处理解码过程的地址的字符串。 默认为 Hidden

泛型属性句柄

大多数控件属性都将作为控件的属性来处理。 例如 Button 控件可以定义如下:

<asp:Buttonid="subCmd"runat="server"Text="Submit"></asp:Button>

这里的Text 属性用于填充控件中的.Text 属性。 NoSpamEmailHyperlink 控件包含四个这样的属性: .Email.ScrambleSeed.EncodeInText.HideText

这些属性的代码没有多少变化。

[Bindable(true), 
Category("Data"), 
DefaultValue(""),
Description("Email address for hyperlink")]publicvirtualstring Email
{
 get {
 return (ViewState["Email"] isstring)
? (string) ViewState["Email"]
 : String.Empty;
 }
 set {
 ViewState["Email"] = value;
 }
}

这里有两个重要的地址。

属性

每个属性都有 BindableCategoryDefaultValueDescription 属性。

注意,这个上下文中的"属性"不同于 html/xml属性,它是一个属性。类或者方法中的.NET 框架属性。 当讨论网页控件时,同时提到这两者是不可避免的,尽管这两者完全无关。

这些属性都不会对运行时呈现控件的方式产生任何影响。 它们只是WYSIWYG设计器( 如在属性页中处理属性时使用的Visual Studio. NET) )的信息。

可以绑定( true ) 通知设计器应该包含在to对话框中,通常通过单击 (DataBindings) 伪属性旁边的椭圆(。) 按钮访问。 当然,把这个属性包含在一起并不会有什么好处,直到你有一个很好的理由排除它。

CategoryAttribute 帮助设计器中的属性页对相似属性的属性进行分组。 Category("Data") 将属性与 (DataBindings) 组合在一起,这在许多情况下是最有用的位置。 如果你希望使用不同的类别,但是如果没有首选项,则使用 Data,因为默认类别为 Misc

WebControlDefaultValueAttribute 告诉属性页是否为"重置"( 右键单击,在 Visual Studio. NET) 中重置,或者在输入时停止属性)。 这应该始终与 ViewState 值( 请参见下面) 所在的代码中返回的值相同

注意,Web窗体控件中的持久性与 Windows 窗体控件中的持久性处理非常不同。

DescriptionAttribute 定义一些可以由设计器拾取以在属性旁边显示的文本。 例如在 Visual Studio. NET 中,说明显示为属性页,可以使用拆分器显示或者隐藏。

ViewState

当请求时,该属性检查相关的ViewState 值,如果 如果类型( 只有伪造申请的可能) 不正确,则返回默认值。 如果 ViewState 值对属性有效,则将它的转换为正确的类型并返回。

ViewStateStateBag 类型的对象。 在呈现页面时,将每个控件的ViewState 中的所有值优化并组合到单个"隐藏"输入字段中。

这允许在 postback 上保留值,但是如果页面开发人员不需要这个值,则可以使用 .EnableViewState 属性关闭。

请注意,如果希望包含非基本类型的类型的属性( 字符串 , bool , int , 如果是 C# 中的关键字) 或者这些类型的array,则必须创建 TypeConverter 类来告诉 StateBag 如何优化它。 本文的详细内容超出了本文的范围。

PersistenceModeAttribute

上面 属性不使用 PersistenceModeAttribute,因为它们允许默认为 PersistenceMode.Attribute。 还有三种其他类型的PersistenceMode,通常应用于控件中的属性: .InnerProperty.DefaultInnerProperty.EncodedDefaultInnerProperty

关于这个属性的使用有一些常见的误解,可以很容易地混淆第一个web控件开发者。 在这里寻址将帮助解释我们必须进行的过程,使 .Text 属性处理控件的内部文本。

应用于属性样式属性的属性一样,PersistenceModeAttribute 在运行时没有效果。 它只告诉设计者在HTML中获取或者设置属性值的位置。

PersistenceMode.EncodedDefaultInnerProperty

实际上,所有属性都可以定义为内部属性或者属性属性。 例如下面的任何一个都是同一个 Hyperlink 控件的同样有效的定义:

<asp:HyperLinkRunat="server"id="hl"Text="CodeProject"NavigateUrl="http://www.codeproject.com"></asp:HyperLink>
<asp:HyperLinkRunat="server"ID="hl"Text="CodeProject"><NavigateUrl>http://www.codeproject.com</NavigateUrl></asp:HyperLink>
<asp:HyperLinkRunat="server"ID="hl"NavigateUrl="http://www.codeproject.com"> CodeProject</asp:HyperLink>

在默认情况下,设计器将使用最后一个定义,因为 .NavigateUrl 属性没有用 PersistenceModeAttribute ( 因此默认为 PersistenceMode.Attribute ) 标记,而 .Text 属性标记为 as。 PersistenceMode.EncodedDefaultInnerProperty

请注意,在 VS.NET 2002和 2003中,有一个例外: 使用 DataBindings dialog绑定内部属性时,设计器将它的作为属性属性持久化。

如果 .Text 属性被标记为 PersistenceMode.InnerProperty,那么它将以相同的方式持久化,但被 <Text>...</Text> 标记包围。 告诉设计者不要使用标签,而告诉设计者在持久化属性时对属性进行编码( 例如 ( ) persisting。 < 成为 &lt; )。

NoSpamEmailHyperlink 控件使用相同的策略:

[Bindable(true), 
Category("Data"), 
DefaultValue(""),
PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]publicvirtualstring Text 
{
 get {
 return (ViewState["Text"] isstring)
? (string) ViewState["Text"]
 : String.Empty;
 }
 set {
 ViewState["Text"] = value;
 }
}

注意,对于我们的html属性样式属性 上面,内部属性使用相同的属性,并且 .Text 属性仍在使用 ViewState

惟一的区别是我们添加了 PersistenceModeAttribute,但是要记住这个更改只通知设计器如何处理属性 table 中的更新。 它不告诉控件在运行时如何处理这种内部内容。 就它的自身而言 PersistenceMode.InnerDefaultProperty 属性会导致控件的行为异常怪异。 我们需要做一些进一步的更改来处理这里功能。

ParseChildrenAttribute

首先,我们需要告诉控件在处理它们之前不要解析子对象。 我们通过重写控件上的默认 ParseChildrenAttribute 来实现。

[//...
ParseChildren(false),
//...]publicclass NoSpamEmailHyperlink : System.Web.UI.WebControls.WebControl
{//.. .}
AddParsedSubObject

我们还需要重写 .AddParsedSubObject() 方法来处理文本内容,该内容被解析为控件。

protectedoverridevoid AddParsedSubObject(object obj)
{
 if (obj is LiteralControl)
 {
 // If inner text is a literal, we can// pick up the text at any point and// populate our Text property Text = ((LiteralControl)obj).Text;
 }
 else 
 {
 // If inner text is databound, we need to // pick up the text at render time, so // we'll add the control as a child//// NoSpamEmailHyperlinkBuilder.AppendSubBuilder// will throw out any other child controls at// parse-time, no other condition should exist base.AddParsedSubObject(obj);
 }
}

在设计时和运行时,页分析器会调用 .AddParsedSubObject() 方法来添加子控件。 我们感兴趣的唯一子控件类型是:

  • LiteralControl - 控件文本的内部为 static 时。
  • DataBoundLiteralControl - 当控件的内部包含数据绑定代码块时('' <@#。@> ') 在运行时。
  • DesignerDataBoundLiteralControl - 在设计时控制文本内部包含数据绑定代码块的时间
ControlBuilder

如果控件类型无法在任何列出的中运行,那么我们可以通过抛出异常来拒绝其他的。 但是,通过提供自定义 ControlBuilder 并重写 .AppendSubBuilder() 方法,这样做是更有效的。 这是因为每个控件都是由解析器创建的,因这里可以节省创建异常的许多控件的努力。

publicclass NoSpamEmailHyperlinkBuilder : ControlBuilder
{//...publicoverridevoid AppendSubBuilder(ControlBuilder subBuilder) 
 {
 if (subBuilder.ControlType == null)
 {
 // This allows codeblocks to be added in the// inner text of the controlbase.AppendSubBuilder(subBuilder);
 }
 else {
 thrownew InvalidOperationException(
 String.Format(
 "Control {0} may not contain {1}", 
 ControlType.FullName, 
 subBuilder.ControlType.FullName
 )
 );
 }
 }
}

如果控件内容为文本文本,仅当存在数据绑定代码块和/或者子控件时,从不调用这里方法。

数据绑定代码 block 生成器( 其中 subBuilder.ControlType ==空 ) 应按正常方式处理。 在设计时,任何其他控件都应该抛出异常,它被解析器捕获并在设计时变成错误标记或者在运行时出错。

在生成自定义设计器时,我们还应该重写 .AllowWhiteSpaceLiterals 属性。

publicclass NoSpamEmailHyperlinkBuilder : ControlBuilder
{
 publicoverridebool AllowWhitespaceLiterals() 
 {
 returnfalse;
 }//.. .}

默认情况下,返回 true 表示只包含空白(。空格,制表符,新行)的文字将被接受。 把这一切都重写了 false 仅包含空白的文字将被视为

对于 NoSpamEmailHyperlink,确保控件始终可见,除了在运行时 .Email.Text 属性都是空的。

我们还可以通过重写 ControlBuilder 中的属性来完成许多其他事情,但是对于 NoSpamEmailHyperlinkBuilder,这是足够的。 查看. NET 框架中的其他类,看看定制 ControlBuilder 类可以使用多少种方式。

把松散的末端

最后,我们通过添加 ControlBuilderAttribute 将新的NoSpamEmailHyperlinkBuilder 类附加到 NoSpamEmailHyperlink 控件。

[//...
ControlBuilder(typeof(NoSpamEmailHyperlinkBuilder)),
//...]publicclass NoSpamEmailHyperlink : System.Web.UI.WebControls.WebControl
{//.. .}

呈现控件

实际呈现控件所需的代码现在非常简单。

TagKey

在呈现HTML对象的任何控件中,.TagKey 属性都被重写,而不是默认 <SPAN> 在开发超链接控件时,主要标记类型为 <>

protectedoverride HtmlTextWriterTag TagKey
{
 get {
 return HtmlTextWriterTag.A;
 }
}
AddAttributesToRender

如果设置了 .Email 属性,则需要添加 href 属性,我们通过编码电子邮件地址来构建这里属性,并将 mailto: 子句。

无论如何,我们还需要呈现任何格式属性,因这里调用 base 实现也很重要。

protectedoverridevoid AddAttributesToRender(HtmlTextWriter writer)
{
 if (Email.Length >0)
 {
 writer.AddAttribute(
 HtmlTextWriterAttribute.Href,
 "mailto:" + Encode(Email)
 );
 }
 base.AddAttributesToRender (writer);
}
RenderContents

呈现控件的内容( 例如。 文本之间的文本 <>。</A> 首先,我们需要确定是否有任何子控件剩余。

如果有 DataBoundLiteralControl 类型的子元素,则应该只有一个。 在这个控件的生命周期中,已经处理了数据绑定,我们可以简单地检索 NoSpamEmailHyperlink.Text 属性。 同时,DesignerDataBoundLiteralControl 类型的子控件被用来填充设计器中看到的文本,因此我们可以忽略这些。

如果子控件不是 DataBoundLiteralControl 类型的,那么我们可以直接处理这个控件的.Text。 如果已经设置 .Text 属性,则应使用这里属性来呈现控件文本。 如果 .Text 属性为空,则应在它的位置使用 .Email 属性。

如果我们有要呈现字符串的内容,我们可以 Encode 地址( 如有必要并要求),HtmlEncode,然后把它写到开始和结束标签之间。

protectedoverridevoid RenderContents(HtmlTextWriter writer)
{
 // The only controls that should be left at this// point should be DataBoundLiteralControls. In theory,// if there are any then there should be exactly one // and nothing else.string displayText = null;
 if (Controls.Count >0 && Controls[0] is DataBoundLiteralControl)
 {
 displayText = ((DataBoundLiteralControl)Controls[0]).Text;
 }
 else {
 // If there is some text, use it. If not then display the// encoded email address or the hyperlink will not be// visible displayText = (Text.Length == 0)
? Email
 : Text;
 }
 // If the EncodeInText flag is set and the email address// is somewhere in the Text, encode it.if (EncodeInText && Email.Length >0)
 {
 int idx = displayText.IndexOf(Email);
 if (idx > -1) displayText = BrowserNeedsHide? 
 HideText : Encode(displayText, Email);
 }
 // Html encode any text that remains. writer.Write(HttpUtility.HtmlEncode(displayText));
}

BrowserNeedsHide 属性只检查 Page.Request.Browser的版本 4.x 或者 below,在链接属性的innerHTML 中解码电子邮件地址是不可能的。

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]protectedvirtualbool BrowserNeedsHide
{
 get {
 // If the Browser is Netscape (v4.x or less), we cannot change// the innerHTML at run time, so we'll just hide it HttpBrowserCapabilities bc = Page.Request.Browser;
 Version bv = new Version(bc.Version);
 return (bc.Browser.ToLower().IndexOf("netscape") > -1 
 && bv.Major <5);
 }
}

如果你想让我们的客户知道,我们也不需要将他们标识为 Netscape 4.5,我们也不希望这些地址被编码为在客户端进行解码,但我们也不希望 smart。 因此,我们使用 .HideText 属性的内容替换电子邮件地址,而不是编码地址。

那当然 href 在这些情况下,属性被编码和解码,因此对功能没有负面影响。

结束语

如果我们现在假设 .Encode() 方法不做任何操作,只返回它所给的字符串,一个 NoSpamEmailHyperlink 控件:

<cpspam:NoSpamEmailHyperlinkid="nseh"runat="server"Email="pdriley@santt.com"ScrambleSeed="181"> Paul Riley (pdriley@santt.com)</cpspam:NoSpamEmailHyperlink>

将呈现以下 HTML。

Netscape 4.x 或者更早版本
<aid="nseh"href="mailto:pdriley@santt.com"> Paul Riley ([Hidden])</a>
其他浏览器
<aid="nseh"href="mailto:pdriley@santt.com"> Paul Riley (pdriley@santt.com)</a>

这正是我们在电子邮件超链接控件中所要查找的格式。 唯一缺失的就是编码和解码功能。 下一篇文章将着眼于修复。

现在我们已经检查了一些方法来保持属性,并且实现了最简单和最复杂的属性。

我们查看了 ControlBuilder 类的一个简单实现,并将属性操作视为呈现过程的一部分。

这足够让我们在自定义控件的世界中开始,但在许多情况下,我们需要更多。 接下来,我们将看一下JavaScript注册函数,以及如何用于在单页上操作控件的任意数量的实例。

修订历史

  • 1.0 12 -Oct-2003 - 已经创建。
  • 1.1 23 -Oct-2003 - 添加了 .HideText 属性。

PROP  
相关文章