• ASP.NET进阶(6):认清控件 之 Render


       很久没写了,抱歉,呵呵。上节我们主要讲了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<stringstring>();
            }
            
    public string TagName { getset; }
            
    public string ID { getset; }
            
    public string Text { getset; }
            
    public string Name { getset; }
            
    public IDictionary<stringstring> Attributes { getset; }
            
    public ButtonType Type { getset; }
            
    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 { getset; }
            
    public string Name { getset; }
            
    public string TagName { getset; }
            
    public string TagEndHtml { getset; }
            
    public ControlAttributes Attributes { getset; }
        }
        
    public class ControlAttributes
        {
            
    private readonly IDictionary<stringstring> _attributes;
            
    public ControlAttributes()
            {
                _attributes 
    = new Dictionary<stringstring>();
            }
            
    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 { getset; }
            
    public ButtonType Type { getset; }
            
    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。


  • 相关阅读:
    pyroscope 参考使用
    pyroscope 很不错的基于golang 的火焰图分析工具
    dremio 14 版本发布&&新的官方文档页面
    sijms/go-ora 1.0 发布了,使用buffer提升了系统的性能
    开发一个cockroachdb 的cube.js 驱动
    dremio 配置文件
    cratedb 将完全开源
    jfrog 关闭开放 bintray&&jcenter&&gocenter&&chartcenter 服务
    dremio tar 模式安装
    dremio 部署系统要求
  • 原文地址:https://www.cnblogs.com/mad/p/1872217.html
Copyright © 2020-2023  润新知