在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
很久没写了,抱歉,呵呵。上节我们主要讲了Click的流程,这次主要来看HTML输出。
假如让你写一个Button控件类,你如何设计这个类?他应该包含什么内容?
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类没有这个类型成员,神了!是外部调用传进来的。 他到底干什么的?
代码
上面这些都是html标签的基本元素,TagName和括号。似乎有点眉目了,和我们写的差不离嘛:)再看看他的 其他方法:
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, ...................... }
代码
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)都设计成了枚举,这样比较好,枚举使代码易读而且要比字符串处理更快,这些名称也很少有变化。
代码
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); } Button控件在输出的时候是没有Contents的,所以我们看到他只是重写了RenderContents方法,方法体就是空。
代码
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)//判断怎么关。。 { t |
请发表评论