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 < 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。


你可能感兴趣的:(ASP.NET进阶(6):认清控件 之 Render)