OK!此类必须有个输出HTML的方法对吧?Render,还需要所包含的TagName、Value、Id、Name、Type、Class等等我们能想到的的<input>的属性。这些具体的属性在类里设计成属性就行了,还有一些自定义的属性,所以还需要一个AddAttribute方法。那么我们写一下大致的雏形。
public
class
Button
{
public
enum
ButtonType
{
Submit, Button, Reset
}
public
Button()
{
Attributes
=
new
Dictionary
<
string
,
string
>
();
}
public
string
TagName {
get
;
set
; }
public
string
ID {
get
;
set
; }
public
string
Text {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
IDictionary
<
string
,
string
>
Attributes {
get
;
set
; }
public
ButtonType Type {
get
;
set
; }
public
void
AddAttribute(
string
name,
string
value)
{
Attributes.Add(name, value);
}
private
string
GetAttributeHtml()
{
var html
=
string
.Empty;
foreach
(var item
in
Attributes)
{
html
+=
"
"
+
item.Key
+
"
=\
""
+ item.Value +
"
\
""
;
}
return
html;
}
public
string
Render()
{
return
String.Format(
"
<{0} type=\
"
{
1
}\
"
id=\
"
{
2
}\
"
name=\
"
{
3
}\
"
value=\
"
{
4
}\
"
{5} />
"
, TagName, Type, ID, Name, Text, GetAttributeHtml());
}
}
OK,如何?简版Button完成,可以输出基本的Button标签。先不说性能如何,您觉得能用吗?微软会这么写吗?! 显然不会,这太简陋了,而且也没考虑到面向对象,因为标签都具备公共特性,比如 TagName,id ,name,class等,那我们改良下。
namespace
WebApplication1.Control
{
public
interface
IControl
{
void
Render();
}
public
class
Control
{
public
string
ID {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
TagName {
get
;
set
; }
public
string
TagEndHtml {
get
;
set
; }
public
ControlAttributes Attributes {
get
;
set
; }
}
public
class
ControlAttributes
{
private
readonly
IDictionary
<
string
,
string
>
_attributes;
public
ControlAttributes()
{
_attributes
=
new
Dictionary
<
string
,
string
>
();
}
public
void
AddAttribute(
string
name,
string
value)
{
_attributes.Add(name, value);
}
public
string
GetAttributeHtml()
{
var html
=
string
.Empty;
foreach
(var item
in
_attributes)
{
html
+=
"
"
+
item.Key
+
"
=\
""
+ item.Value +
"
\
""
;
}
return
html;
}
}
public
class
Button : Control, IControl
{
public
enum
ButtonType
{
Submit, Button, Reset
}
public
Button()
{
TagEndHtml
=
"
/>
"
;
}
public
string
Text {
get
;
set
; }
public
ButtonType Type {
get
;
set
; }
public
void
Render()
{
HttpContext.Current.Response.Write(String.Format(
"
<{0} type=\
"
{
1
}\
"
id=\
"
{
2
}\
"
name=\
"
{
3
}\
"
value=\
"
{
4
}\
"
{5} {6}
"
, TagName, Type, ID, Name, Text, Attributes.GetAttributeHtml(),TagEndHtml));
}
}
}
似乎有点感觉了,提取了公共部分,也抽象了Control,还把Attribute单独设计成类(职责单一嘛),但是,要想弄一个真正周全的Button,远没有这么简单,来一起看看微软官方的实现吧:)
首先看继承关系:
public
class
Button : WebControl, IButtonControl, IPostBackEventHandler
public
class
WebControl : Control, IAttributeAccessor
public
class
Control : IComponent, IDisposable, IParserAccessor, IUrlResolutionService, IDataBindingsAccessor, IControlBuilderAccessor, IControlDesignerAccessor, IExpressionsAccessor
所有的控件都是Control,所以需要一个Control基类,而微软又把控件分为了服务端控件(WebControl)和Html控件,所以所有的服务端控件又继承与WebControl。
我们知道Html控件就是我们常用的Html标签加上 runat="server"。所以功能方面要比WebControl差的远,自然就分开了。
我们来看看这些类,Control有个Render方法虚方法,这个设计是合理的,有些控件不一定非要自己实现输出,所以设计成接口不合理。
protected
internal
virtual
void
Render(HtmlTextWriter writer)
{
this
.RenderChildren(writer);
}
//而内容也很简洁,就是输出子控件。
protected
internal
virtual
void
RenderChildren(HtmlTextWriter writer)
{
ICollection children
=
this
._controls;
this
.RenderChildrenInternal(writer, children);
}
internal
void
RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
{
if
((
this
.RareFields
!=
null
)
&&
(
this
.RareFields.RenderMethod
!=
null
))
{
writer.BeginRender();
this
.RareFields.RenderMethod(writer,
this
);
writer.EndRender();
}
else
if
(children
!=
null
)
{
foreach
(Control control
in
children)
{
control.RenderControl(writer);
}
}
}
如果是特殊的控件就输出开始 内容和结束,如果不是则调用子控件的所有输出。这些方法都是虚方法,可以被子类覆盖。而我们需要关注的是一个类HtmlTextWriter。
是的,所有的输出都没离开他不是吗?而且Control类没有这个类型成员,神了!是外部调用传进来的。 他到底干什么的?
public
class
HtmlTextWriter : TextWriter
{
public
const
char
DoubleQuoteChar
=
'
"
'
;
public
const
string
EndTagLeftChars
=
"
</
"
;
public
const
char
EqualsChar
=
'
=
'
;
public
const
string
EqualsDoubleQuoteString
=
"
=\
""
;
private
int
indentLevel;
public
const
string
SelfClosingChars
=
"
/
"
;
public
const
string
SelfClosingTagEnd
=
"
/>
"
;
public
const
char
SemicolonChar
=
'
;
'
;
public
const
char
SingleQuoteChar
=
'
\
'
;
public
const
char
SlashChar
=
'
/
'
;
public
const
char
SpaceChar
=
'
'
;
public
const
char
StyleEqualsChar
=
'
:
'
;
private
bool
tabsPending;
private
string
tabString;
public
const
char
TagLeftChar
=
'
<
'
;
public
const
char
TagRightChar
=
'
>
'
;
protected
HtmlTextWriterTag TagKey
{
get
{
return
this
._tagKey;
}
set
{
this
._tagIndex
=
(
int
) value;
if
((
this
._tagIndex
<
0
)
||
(
this
._tagIndex
>=
_tagNameLookupArray.Length))
{
throw
new
ArgumentOutOfRangeException(
"
value
"
);
}
this
._tagKey
=
value;
if
(value
!=
HtmlTextWriterTag.Unknown)
{
this
._tagName
=
_tagNameLookupArray[
this
._tagIndex].name;
}
}
}
}
public
enum
HtmlTextWriterTag
{
Unknown,
A,
Acronym,
Address,
Area,
B,
Base,
Basefont,
Bdo,
......................
}
上面这些都是html标签的基本元素,TagName和括号。似乎有点眉目了,和我们写的差不离嘛:)再看看他的 其他方法:
private
void
AddAttribute(
string
name,
string
value, HtmlTextWriterAttribute key,
bool
encode,
bool
isUrl)
{
RenderAttribute attribute;
if
(
this
._attrList
==
null
)
{
this
._attrList
=
new
RenderAttribute[
20
];
}
else
if
(
this
._attrCount
>=
this
._attrList.Length)
{
RenderAttribute[] destinationArray
=
new
RenderAttribute[
this
._attrList.Length
*
2
];
Array.Copy(
this
._attrList, destinationArray,
this
._attrList.Length);
this
._attrList
=
destinationArray;
}
attribute.name
=
name;
attribute.value
=
value;
attribute.key
=
key;
attribute.encode
=
encode;
attribute.isUrl
=
isUrl;
this
._attrList[
this
._attrCount]
=
attribute;
this
._attrCount
++
;
}
微软把Attribute也设计成了类,而且他没有用Dictionary<T,T>,而是RenderAttribute数组。有意思的是默认20个长度,太多还要搞个双倍的数组重新copy过去。所以大家不要搞太多的属性哦:)
他们把所有的标签(HtmlTextWriterTag、属性(HtmlTextWriterAttribute)和样式属性(HtmlTextWriterStyle)都设计成了枚举,这样比较好,枚举使代码易读而且要比字符串处理更快,这些名称也很少有变化。
回头看下WebControl类,因为所有的服务端控件都继承此类,所以他的Render方法也是一个虚方法,子类可以复写。
protected
internal
override
void
Render(HtmlTextWriter writer)
{
this
.RenderBeginTag(writer);
this
.RenderContents(writer);
this
.RenderEndTag(writer);
}
public
virtual
void
RenderBeginTag(HtmlTextWriter writer)
{
this
.AddAttributesToRender(writer);
HtmlTextWriterTag tagKey
=
this
.TagKey;
if
(tagKey
!=
HtmlTextWriterTag.Unknown)
{
writer.RenderBeginTag(tagKey);
}
else
{
writer.RenderBeginTag(
this
.TagName);
}
}
protected
internal
virtual
void
RenderContents(HtmlTextWriter writer)
{
base
.Render(writer);
}
public
virtual
void
RenderEndTag(HtmlTextWriter writer)
{
writer.RenderEndTag();
}
方法够简洁,开始->内容->结束。其中开始和结束调用的都是writer的方法,而内容则是base(即Control)的Render方法,上面已经贴了。
回头看Render调用的是RenderControl
public
virtual
void
RenderControl(HtmlTextWriter writer)
{
this
.RenderControl(writer,
this
.Adapter);
}
所以总体来说,输出内容的话 既可以重写RenderContents方法,也可以重写RenderControl方法,也可以重写Render方法。
Button控件在输出的时候是没有Contents的,所以我们看到他只是重写了RenderContents方法,方法体就是空。
LinkButton是<a>连接,中间就得有text,所以他的方法就是输出Text了。
而CheckBox类则是重写的Render方法!因为CheckBox生成的代码有点特殊 他包含了<label>和<input> 默认的Render方法三步骤不适合他,所以必须重写Render。你可能会问DropDownList应该也重写的Render方法吧,因为他有<select>和<option>,其实。。 他谁都没重写?Why?因为他是<dorpdownlist><item>这种形式,也就是父控件包含子控件,而且都遵循3步骤,所以默认就OK了。
说完RenderContent,再说前后两个步骤RenderBeginTag和RenderEndTag。
EndTag是很简单,就是 </TagName>,没有什么花样,而BeginTag就复杂写,他要把属性、样式和其他一大堆属性(除了内容)都要加进去,而且最后判断是否需要 > 或 /> 关闭。
public
virtual
void
RenderBeginTag(HtmlTextWriterTag tagKey)
{
this
.TagKey
=
tagKey;
bool
flag
=
true
;
if
(
this
._isDescendant)
{
flag
=
this
.OnTagRender(
this
._tagName,
this
._tagKey);
this
.FilterAttributes();
string
str
=
this
.RenderBeforeTag();
if
(str
!=
null
)
{
if
(
this
.tabsPending)
{
this
.OutputTabs();
}
this
.writer.Write(str);
}
}
TagInformation information
=
_tagNameLookupArray[
this
._tagIndex];
TagType tagType
=
information.tagType;
bool
flag2
=
flag
&&
(tagType
!=
TagType.NonClosing);
string
endTag
=
flag2
?
information.closingTag :
null
;
if
(flag)
{
if
(
this
.tabsPending)
{
this
.OutputTabs();
}
this
.writer.Write(
'
<
'
);
//
这是输出标签开始
this
.writer.Write(
this
._tagName);
//
标签名称
string
str3
=
null
;
for
(
int
i
=
0
; i
<
this
._attrCount; i
++
)
//
杯具的属性循环
{
RenderAttribute attribute
=
this
._attrList[i];
if
(attribute.key
==
HtmlTextWriterAttribute.Style)
{
str3
=
attribute.value;
}
else
{
this
.writer.Write(
'
'
);
this
.writer.Write(attribute.name);
if
(attribute.value
!=
null
)
{
this
.writer.Write(
"
=\
""
);
string
url
=
attribute.value;
if
(attribute.isUrl
&&
((attribute.key
!=
HtmlTextWriterAttribute.Href)
||
!
url.StartsWith(
"
javascript:
"
, StringComparison.Ordinal)))
{
url
=
this
.EncodeUrl(url);
}
if
(attribute.encode)
{
this
.WriteHtmlAttributeEncode(url);
}
else
{
this
.writer.Write(url);
}
this
.writer.Write(
'
"
'
);
}
}
}
if
((
this
._styleCount
>
0
)
||
(str3
!=
null
))
//
杯具的样式输出
{
this
.writer.Write(
'
'
);
this
.writer.Write(
"
style
"
);
this
.writer.Write(
"
=\
""
);
CssTextWriter.WriteAttributes(
this
.writer,
this
._styleList,
this
._styleCount);
//
还有专门的CssWriter 继续杯具的循环样式
if
(str3
!=
null
)
{
this
.writer.Write(str3);
}
this
.writer.Write(
'
"
'
);
}
if
(tagType
==
TagType.NonClosing)
//
判断怎么关。。
{
this
.writer.Write(
"
/>
"
);
}
else
{
this
.writer.Write(
'
>
'
);
}
}
string
str5
=
this
.RenderBeforeContent();
if
(str5
!=
null
)
{
if
(
this
.tabsPending)
{
this
.OutputTabs();
}
this
.writer.Write(str5);
}
if
(flag2)
{
if
(tagType
==
TagType.Inline)
{
this
._inlineCount
++
;
}
else
{
this
.WriteLine();
this
.Indent
++
;
}
if
(endTag
==
null
)
{
endTag
=
"
</
"
+
this
._tagName
+
'
>
'
.ToString(CultureInfo.InvariantCulture);
}
}
if
(
this
._isDescendant)
{
string
str6
=
this
.RenderAfterTag();
if
(str6
!=
null
)
{
endTag
=
(endTag
==
null
)
?
str6 : (str6
+
endTag);
}
string
str7
=
this
.RenderAfterContent();
if
(str7
!=
null
)
{
endTag
=
(endTag
==
null
)
?
str7 : (str7
+
endTag);
}
}
this
.PushEndTag(endTag);
this
._attrCount
=
0
;
this
._styleCount
=
0
;
}
ok,今天说了下Render,呵呵,其实大都是在说类的设计。弄清Freamwork的同时,我们也来领会微软的设计。至于具体的自定义控件开发大家可以搜索下,会有很多的文章。无外乎就是AddAttribtue和Render。