4. 控制复杂的内容
简单地重写AddParsedSubObject方法就可以处理复杂的内容,但如上所述,该过程有以下几个缺陷。
● 不进行编辑,无法确保只显示有效的标记
● 不能管理把嵌套标记转换为元素的过程
● 忽略了HTML编码(例如用>替换>)
● 从一个标记结束到另一个标记开头之间的空白处理为字面文本,传送给AddParsedSubObject方法
● 所有嵌套的元素都必须指定相应的前缀和runat特性
使用控件构建器类和自定义控件,就可以更多地控制在自定义控件中处理标记的过程。ASP.NET在控件内部处理标记,而控件构建器作为该过程的一部分调用,之后把得到的对象传送给AddParsedSubObject方法。例如,在控件构建器上调整属性,就不再需要把空白传送给AddParsedSubObject方法,而可以控制从嵌套标记中生成的对象类型。
提示:
每个ASP.NET控件都自动调用ASP.NET附带的默认控件构建器,或调用专用的控件构建器。
控件构建器是一个继承了System.Web.UI.ControlBuilder的类,如下面的Visual Basic 2005示例所示。
Public Class BookBuilder
Inherits System.Web.UI.ControlBuilder
End Class
C#的对应代码如下所示。
public class BookBuilder : System.Web.UI.ControlBuilder
{
}
控件构建器通过自定义控件的类声明中的ControlBuilder属性,关联到自定义控件上。这个特性必须传送自定义控件使用的控件构建器类型。在Visual Basic 2005中,下面的类声明把BookBuilder控件构建器与BookInfo自定义控件关联起来。
<ControlBuilder(GetType(BookBuilder)), _
ToolboxData("<{0}:BookInfo runat=server></{0}:BookInfo>")> _
Public Class BookInfo
C#的对应代码如下所示。
[ControlBuilder(typeof(BookBuilder))]
[ToolboxData("<{0}:BookInfo runat=server></{0}:BookInfo>")]
public class BookInfo
可以使用控件构建器简化AddParsedSubObject方法的处理。例如,在默认情况下,控件构建器把从一个标记结束到另一个标记开头之间的空白返回为一个LiteralControl。在大多数情况下,我们都不希望在AddParsedSubObject方法中处理该文本。重写控件构建器的AllowWhite spaceLiteral属性,使之返回False,就可以抑制空白。也可以重写HTMLDecodeLiterals方法,并返回False,把要解码的所有HTML字面文本都传送给AddParsedSubObject方法。
重写AllowWhitespaceLiterals属性会禁止把所有的空白传送给AddParsedSubObject方法,但不能禁止把标记外部的文本传送给该方法。重写控件构建器的AppendLiteralString方法,可以禁止把字面文本传送给AddParsedSubObject。
下面的Visual Basic 2005示例解码HTML,禁止把空白和字面文本传送给自定义控件的AddParsedSubObject方法。
Public Class BookBuilder
Inherits System.Web.UI.ControlBuilder
Overrides Public Function HtmlDecodeLiterals() As Boolean
Return True
End Function
Public Overrides Function AllowWhitespaceLiterals() As Boolean
Return False
End Function
Public Overrides Sub AppendLiteralString(ByVal s As String)
End Sub
End Class
C#的对应代码如下所示。
public class BookBuilder : System.Web.UI.ControlBuilder
{
public override bool HtmlDecodeLiterals()
{
return true;
}
public override bool AllowWhitespaceLiterals()
{
return false;
}
public override void AppendLiteralString(string s)
{
}
}
也可以使用控件构建器的GetChildControlType方法,简化自定义控件中使用的标记或把不同的标记作为同一个对象来处理。GetChildControlType方法为每个嵌入到自定义控件中的标记调用一次,返回标记所用的对象类型。要给GetChildControlType方法传送标记的名称和包含标记上所有特性的IDictionary对象。这样,就可以调整标记使用的对象,不再需要完全限定内嵌在自定义控件中的标记了。这个方法还可以测试自定义控件中的内容,确定如何处理它们。
例如,使用控件构建器的GetChildControlType方法,可以给BookInfo标记重写嵌入的控件,删除前缀和runat特性,如下所示。
<cc1:BookInfo ID="BookInfo1" runat="server" >
<Author FirstName="Peter" LastName="Vogel" />
<Titles MainTitle="Custom Controls and Web Parts" SubTitle="ASP.NET 2.0" />
<asp:Label ID="Label1" runat="server" Text="Your book"></asp:Label>
</cc1:BookInfo>
提示:
与把属性值保存为控件中的内容一样,我们可能遇到Visual Studio .NET伪造的错误:模式不支持这些嵌入的标记。可以忽略这些错误。
在GetChildControlType方法中,可以继承每个标记名,返回该标记使用的对象类型(该对象类型将传送给自定义控件的AddParsedSubObject方法)。没有这个处理,上一个例子中的所有非ASP.NET标记都会被看做字面文本,因为它们没有前缀或runat特性。
下面的Visual Basic示例设置了每个标记的数据类型,还将检查不应显示的标记。
Public Class BookBuilder
Inherits System.Web.UI.ControlBuilder
Public Overrides Function GetChildControlType( _
ByVal tagName As String, ByVal attribs As System.Collections.IDictionary) _
As System.Type
Select Case tagName
Case "Author"
Return GetType(Author)
Case "Titles"
Return GetType(Titles)
Case "Description"
Return GetType(Description)
Case "asp:Label"
Case Else
Throw New Exception("Tag " & tagName & " not allowed in this control.")
End Select
End Function
End Class
C#的对应代码如下所示。
public class BookBuilder : System.Web.UI.ControlBuilder
{
public override System.Type GetChildControlType(string tagName,
System.Collections.IDictionary attribs)
{
switch(tagName)
{
case "Author":
return typeof(Author);
case "Titles":
return typeof(Titles);
case "Description":
return typeof(Description);
case "asp:Label":
return typeof(Label);
break;
default:
throw new Exception("Tag " + tagName + " not allowed in this control.");
};
}
}
提示:
如上面的示例代码所示,标记名传送给GetChildControlType,其中包含控件使用的前缀。
建立自定义控件时,可以调用其他控件构建器,来建立自定义控件的组成控件。这些组成控件的构建器或者是ASP.NET附带的默认构建器,或者是控件的开发人员提供给控件的定制构建器。
在AppendSubBuilder方法中,可以检查用于每个组成控件的构建器,如果子控件的构建器不支持我们需要的某个功能,就抛出一个异常。下面的Visual Basic 2005示例检查所添加的控件构建器是否支持空白,如果不支持,就抛出一个异常。
Public Overrides Sub AppendSubBuilder( _
ByVal subBuilder As System.Web.UI.ControlBuilder)
If subBuilder.AllowWhitespaceLiterals = False Then
Throw New Exception("Whitespace must be supported.")
Else
MyBase.AppendSubBuilder(subBuilder)
End If
End Sub
C#的对应代码如下所示。
public override void AppendSubBuilder(System.Web.UI.ControlBuilder
subBuilder)
{
if(subBuilder.AllowWhitespaceLiterals == false)
{
throw new Exception("Whitespace must be supported.");
}
else
{
base.AppendSubBuilder(subBuilder);
}
}
提示:
如果重写AppendSubBuilder,就应总是调用底层控件的AppendSubBuilder方法(除非抛出了一个异常)。否则,就不会添加子控件的构建器。
为了支持重用,最好创建自己的控件构建器,这样,每个控件构建器就可以用于多个类型的多种控件了。但是,一般用途的控件构建器可能不能处理这些控件的特定版本。ControlBuilder对象有几个属性,可以找出调用控件构建器的控件信息。
● HasAspCode:如果多种控件在其源代码中嵌入了ASP代码块,该属性就返回True。
● HasBody:如果多种控件有开标记和闭标记(而不是一个空标记),该属性就返回True。
● ControlType:控件构建器关联的自定义控件的Type对象。
● ID:自定义控件的ID属性,控件构建器也可以设置这个值。
● TagName:自定义控件的标记名称。
● FChildrenAsProperties:这个属性由传送给ParseChildren特性的第一个参数设置,当它为True时,表示自动解析自定义控件的内容。
● InPageTheme:如果控件用于生成页面主题,该属性就返回True。
● Localize:如果使用构建器的自定义控件本地化了,该属性就返回True。
除了这些属性之外,还可以通过控件构建器的ControlType属性提取自定义控件的信息。ControlType属性返回自定义控件的类型。使用控件构建器检查自定义控件的代码最好放在控件构建器的OnAppendToParent方法中,在控件构建器关联到自定义控件上时,调用这个方法。
例如,下面的Visual Basic 2005示例检查所建立的控件是否是一个BookInfo控件,如果是,就检查该控件的ChildrenAsProperties属性是否设置为True。如果属性设置为True,代码就抛出一个异常。
Public Overrides Sub OnAppendToParentBuilder(ByVal parentBuilder As
ControlBuilder)
If ControlType.Name = "BookInfo" Then
If Me.FChildrenAsProperties = True Then
Throw New Exception("This builder should not be used with
ParseChildren.")
End If
End If
End Sub
C#的对应代码如下所示。
public override void OnAppendToParentBuilder(ControlBuilder parentBuilder)
{
if(ControlType.Name == "BookInfo")
{
if(this.FChildrenAsProperties == true)
{
throw new Exception(
"This builder should not be used with ParseChildren enabled.");
}
}
}






