關於自定義WEB服務器控件的知識與實例

關於自定義WEB服務器控件的知識與實例  

 

本文重點介紹一種實現控件呈現的常用方法--使用WebControl類的RenderContents方法實現控件呈現。

   基礎知識

    就服務器控件而言只存在兩種情況:一種是具有外觀可視化元素的控件,還有一種是不具有外觀可視化元素的控件。如果需要開發的服務器控件包含可視化元素, 那麼多數情況下,建議開發人員創建繼承自System.Web.UI.WebControls.WebControl基類的控件類。這種做法的主要原因是 基於便捷性考慮。因爲,WebControl類可提供服務器控件的部分與外觀有關的公共屬性、方法和事件等。通過該類定義的屬性,可以控制服務器控件的外 觀和行爲。例如,使用BackColor和ForeColor屬性,可以分別控制服務器控件的背景色和前景顏色;在可以顯示邊框的控件上,可以通過設置 BorderWidth、BorderStyle和BorderColor屬性,控制邊框寬度、邊框樣式和邊框顏色;服務器控件的大小可以通過 Height和Width屬性來指定等等。如果控件基類是Control類,那麼實現這些類似內容則非常繁瑣。

   在使用WebControl基類實現控件呈現的過程中,必然要使用該類所提供的屬性和方法等成員對象。這是讀者需要重點掌握的內容。另外,對於該基類的構造函數也是不容忽視的。下面首先從WebControl的構造函數開始入手進行講解,隨後將說明常見的成員對象。

   WebControl類包括三個構造函數,它們都用於初始化WebControl類的新實例,然而它們之間還存在一些細小的差異。

   (1)protected WebControl ()

   該構造函數用於初始化表示Span HTML元素的WebControl類的新實例。通常情況下,開發人員並不直接調用此構造函數。相反,它通常由派生類的構造函數調用以將TagKey屬性初始化爲Span枚舉值。在隨後的示例中,將重寫TagKey屬性,從而調用此構造函數。

 (2)public WebControl (HtmlTextWriterTag tag)

   開發人員可使用此構造函數創建並初始化使用指定的System.Web.UI.HtmlTextWriterTag值的WebControl類的新實例。其中的參數tag表示HtmlTextWriterTag枚舉值之一。可能讀者對於HtmlTextWriterTag還不太熟悉。它是一個枚舉類型,其枚舉值多爲HTML標記,例如,A、B、Bold、Button等等。

   (3)protected WebControl (string tag)

   使用此構造函數可創建並初始化使用指定的HTML標記的WebControl類的新實例。其中參數tag表示HTML標記。當使用該構造函數時一定要注意:不能直接調用此構造函數。相反,它通常由派生類的構造函數調用以初始化TagKey和TagName屬性

   在瞭解了WebControl類的構造函數之後,讀者還必須瞭解WebControl類的一些常用屬性和方法。下面列舉了這些常用成員對象,它們對於實現控件呈現有着重要意義。

   (1)Attributes屬性

   該屬性用於獲取與控件的屬性不對應的任意特性(只用於呈現)的集合,其屬性類型爲AttributeCollection。

   (2)ControlStyle屬性

   該屬性用於獲取服務器控件的樣式,它是Style類型。ControlStyle屬性封裝WebControl類的所有外觀屬性,如BorderColor和Font。

   (3)TagKey屬性

    該屬性用於獲取與此服務器控件相對應的System.Web.UI.HtmlTextWriterTag值,其屬性類型爲HtmlTextWriterTag枚舉。

   (4)protected virtual void AddAttributeToRender(HtmlTextWriter writer);

    該方法將需要呈現的HTML屬性和樣式添加到指定的System.Web.UI.HtmlTextWriter中。注意在重寫過程中,一定要調用基類中相應的方法

   (5)public void ApplyStyle(Style s);

         該方法將指定樣式的所有非空白元素複製到控件,改寫控件的所有現有的樣式元素。

   (6)public void MergeStyle(Style s);

    該方法將指定樣式的所有非空白元素複製到控件,但不改寫該控件現有的任何樣式元素。

   (7)protected override void Render(HtmlTextWriter writer);方法

    該方法重寫了Control.Render。

   (8)protected virtual void RenderContents(HtmlTextWriter writer);

   該方法將控件的內容呈現到指定的編寫器中。如果要在控件的標籤中寫入文本或其他內容,則需要重寫該方法;如果要使用默認邏輯來呈現子控件,那麼一定要調用基類中相應的方法。

    可能讀者已經注意到WebControl基類中包括的兩個方法:Render和RenderContents。根據上文所介紹的內容可 知,Control基類中包括Render方法。由於WebControl類繼承自Control類,因此,WebControl類中包含Render方 法是無可非議的。然而,WebControl類中卻有一個RenderContents方法,並且該方法與Render方法在功能、參數等方面都非常相 似。那麼在呈現控件過程中到底應該使用哪一個呢?

   實際上,在通常情況下,如果服務器控件自WebControl基類派生,那麼其中的Render方法很少使用,而主要使用RenderContents方法實現控件呈現。爲了說明其中的原因,我們必須瞭解WebControl基類中Render方法的實現邏輯。

   在WebControl基類中的Render方法的實現示意性代碼如下所示:

 protected override void Render(HtmlTextWriter output)
 {
  RenderBeginTag(output);
  RenderContents(output);
  RenderEndTag(output);
 }

   在WebControl基類中的RenderBeginTag方法的實現示意性代碼如下:

 public virtual void RenderBeginTag(HtmlTextWriter output)
 {
  AddAttributesToRender(output);              //將要呈現的控件的屬性添加到HtmlTextWriter對象(流)中
  HtmlTextWriterTag tagKey = TagKey;
  if(tagKey != HtmlTextWriterTag.Unknown)
  {
 output.RenderBeginTag(tagKey);
  } else {
 output.RenderBeginTag(this.TagName);
  }
 }


   在WebControl基類中的RenderContents方法的實現示意性代碼如下:

 protected override void RenderContents(HtmlTextWriter output){
  //使用默認邏輯來呈現子控件,那麼一定要調用基類中的方法。
  base.Render(output);
 }
   分析以上代碼可以得出以下結論:

   一、爲了在由WebControl派生的類中實現控件呈現,必須重寫AddAttributesToRender、RenderBeginTag、RenderEndTag、RenderContents等方法中的一個或者多個,而不必重寫Render方法

    二、重寫AddAttributesToRender、RenderBeginTag、RenderEndTag、RenderContents等方法 非常重要(請注意重寫這些方法的條件及注意事項),否則服務器控件可能會出現丟失標籤的情況,這將嚴重影響服務器控件的呈現。

   三、當呈現服務器控件標籤中的內容時,必須重寫RenderContents方法

   上文介紹了WebControl類的一些基本知識。尤其是對於上文所列舉的示意性代碼需要重點理解。這對於實現控件呈現有着重要作用。

   應用示例

    相信讀者在瀏覽各個網站時,經常會看到"聯繫我們"等類似文字。當單擊這些文字時,操作系統將自動打開自身所帶的郵件客戶端軟件,提示用戶發送郵件。本 例將實現一個自定義服務器控件RenderContentsControl,用於通過呈現包含"mailto:郵件地址"的超鏈接。超鏈接文字是"我的郵 箱地址"。當單擊該鏈接,系統將發送郵件給"[email protected]"。

   在實現以上控件之前,首先應分析如下:本控件包含外觀元素,例如,文字大小、顏色、是否粗體等。爲此,控件基類不應從Control類繼承,而應從WebControl類繼承。這樣,開發人員將不必自行實現這些外觀樣式屬性等內容。

   下面列舉了實現自定義服務器控件的RenderContentsControl.cs文件源代碼。

 using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace CustomControlTest2.App_Code
{
    [
       AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
       AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
        DefaultProperty("Email"),
        ParseChildren(true, "Text"),
       ToolboxData("<{0}:RenderContentsControl runat=server></{0}:RenderContentsControl>")
    ]
    public class RenderContentsControl : WebControl
    {
        // 實現Email屬性
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Email
        {
            get
            {
                String s = (String)ViewState["Email"];
                return ((s == null) ? String.Empty : s);
            }
            set
            {
                ViewState["Email"] = value;
            }
        }
        // 實現Text屬性
        [
         Bindable(true), Category("Appearance"), DefaultValue(""), Localizable(true),
         PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ]
        public virtual string Text
        {
            get
            {
                string s = (string)ViewState["Text"];
                return (s == null) ? String.Empty : s;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }
        // 重寫TagKey屬性
        protected override HtmlTextWriterTag TagKey
        {
            get
            {
                return HtmlTextWriterTag.A;
            }
        }
        // 重寫AddAttributesToRender方法
        protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            base.AddAttributesToRender(writer);
            writer.AddAttribute(HtmlTextWriterAttribute.Href, "mailto:" + Email);
        }
        // 重寫RenderContents方法
        protected override void RenderContents(HtmlTextWriter writer)
        {
            if (Text == String.Empty)
            {
                Text = Email;
            }
            writer.WriteEncodedText(Text);
        }
    }
}


 如 上代碼所示,RenderContentsControl類繼承自WebControl基類,其原因在前文已經說明。另外,在 RenderContentsControl類的實現過程中,還包括了元數據屬性標記、3個屬性(Email、Text和TagKey)和2兩個方法 (AddAttributesToRender和RenderContents)實現等內容。下面逐一對這些內容進行分析。

   代碼說明之3個屬性:

   在上文代碼中主要包括了3個屬性:Email、Text和TagKey。Email屬性用於獲取或者設置具體的電子郵件地址,Text屬性用於獲取或者設置控件顯示的文本內容。在這兩個屬性實現中,都使用了控件視圖狀態ViewState。服務器控件的視圖狀態爲其所有屬性值的累計。對於簡單屬性的實現,將經常使用ViewState。TagKey屬性是重寫屬性,其繼承自WebControl基類,這是讀者需要理解的重點內容。重寫TagKey屬性主要是爲了呈現HTML標記中的a元素,這樣就不會呈現WebControl類所默認呈現的span元素此處,也暗示了本控件使用的構造函數是繼承自WebControl的protected WebControl (),讀者可返回上文再看看有關這個構造函數的說明。需要讀者牢記的是:如果要呈現的元素是HtmlTextWriterTag枚舉的成員,則應重寫TagKey屬性。 許多常見的HTML元素標記被映射爲HtmlTextWriterTag枚舉的值。例 如,System.Web.UI.HtmlTextWriterTag.A與a元素對應,而 System.Web.UI.HtmlTextWriterTag.Table與table元素對應。如果要呈現的元素不是由HtmlTextWriterTag枚舉的成員表示,那麼建議重寫TagName屬性,並返回要作爲元素呈現的字符串。

   代碼說明之2個方法:

   在RenderContentsControl服務器控件中重寫了2個重要方法:一個是AddAttributesToRender、另一個是RenderContents。

   (1)AddAttributesToRender

 該方法用於爲控件添加一個Href屬性,並將該屬性值設置爲"mailto:Email",其中Email是上文所述的表示郵件地址的屬性。當重寫AddAttributesToRender方法時,應始終按照控件源代碼演示的方式:首先,調用基類方法,然後進行相關設置。這樣才能實現爲服務器控件添加樣式和其他屬性的功能。實際上,根據前文所述的RenderBeginTag方法的實現示意性代碼可知,AddAttributesToRender方法是由WebControl的RenderBeginTag方法調用。

   (2)RenderContents

   該方法是本示例的核心內容,其用於在控件的標記中寫入由Text屬性指定的超鏈接文本。如代碼所示,服務器控件調用了HtmlTextWriter實例的WriteEncodedText方法,以對開發人員輸入的文本進行HTML編碼。一般情況下,爲了安全起見,應該對用戶提供的文本進行HTML編碼。

   代碼說明之元數據屬性標記:

   元數據屬性標記在類前和屬性實現前都有所應用。有關這些元數據屬性標記的說明,在以前的文章中已經進行了具體說明。讀者可查閱有關講解如何創建一個簡單的服務器控件的文章。下面重點說明一下有關內部文本持久性的問題

   在類前的元數據屬性標記中,設置了ParseChildren(true, "Text")。在Text屬性前的元數據屬性標記中,設置了PersistenceMode(PersistenceMode.InnerDefaultProperty)。通過以上兩個設置,則爲控件添加了內部文本持久性設置。如此設置,可使得開發人員能夠在控件標記代碼內設置Text屬性。例如:

 <Sample:RenderContentsControl runat="server" ID="CustomerControl" Email="[email protected]">我的郵箱地址</Sample:RenderContentsControl>



   在上面的代碼中,原來文本"我的郵箱地址"應由Text屬性設置(這就是默認持久性),然而,由於內部文本持久性設置,因此,可以在控件標記內設置控件文本。



 實現內部持久性,應使用ParseChildren(true, "Text")來標記RenderContentsControl控件。ParseChildren的第一個參數true,其用於指定頁分析器應將控件標記內的內容分析爲屬性,而不是子控件。第二個參數提供控件的內部默認屬性名稱爲Text。使用這兩個參數調用ParseChildren構造函數時,控件標記內的內容必須與內部默認屬性對應。Text屬性的PersistenceMode(PersistenceMode.InnerDefaultProperty),用於指定可視化設計器應將此屬性作爲控件標記中的內部內容進行序列化。

   通常,WebControl類使用PersistChildren(false)和ParseChildren(true)控制設計時和分析時屬性的持久性。這兩個屬性將被控件繼承,且僅在要更改繼承的設置時需要應用。PersistChildren告知設計器是否應將服務器控件的子控件作爲嵌套的內部控件保存false參數指示內部內容與屬性對應,而不是與子控件對應。ParseChildren已在上一段中加以說明。如果WebControl類的設計時和分析時持久性適用於您的控件,則不必重寫從WebControl繼承的PersistChildren和ParseChildren。

   下面列舉了用於測試服務器控件的Default.aspx文件源代碼:

 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
 <%@ Register TagPrefix="Sample" Assembly="UsingRenderContentsControl" Namespace="UsingRenderContentsControl" %>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head id="Head1" runat="server">
 <title>使用RenderContents方法實現控件呈現</title>
 </head>
 <body>
 <form id="form1" runat="server">
 <div>
 <Sample:RenderContentsControl runat="server" ID="CustomerControl" Font-Bold="true" Font-Size="small" ForeColor="Blue" Email="[email protected]">我的郵箱地址</Sample:RenderContentsControl>
 </div>
 </form>
 </body>
 </html>



 如 上粗體代碼所示,RenderContentsControl控件中設置了Font-Bold、Font-Size、ForeColor、Email等屬 性,同時,還在控件標記之間設置了文本內容。當然,如果開發人員將Text屬性值設置爲相同的文本內容也是可以的。以上代碼比較簡單,在此不再說明了。

    通過這個示例,我們需要重點掌握RenderContents和AddAttributesToRender方法,以及TagKey屬性的使用。提請讀 者注意的是雖然服務器控件的代碼比較複雜,但是結構很簡單。切不可被複雜的樣式設置和客戶端行爲代碼弄得不知所措,而是要注意體會兩個重點方法的使用過 程。

   在服務器控件開發成功之後,最好能夠查看其HTML代碼,並將服務器代碼和HTML代碼作以比較,搞清楚每一條服務器控件代碼 呈現了什麼樣的HTML代碼。通過這個方法,相信讀者能夠對服務器控件的呈現方法有更加深刻的體會。下面列舉了當用戶在瀏覽器中運行以上頁面,並查看相關 的Html源文件時可得到的代碼:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head id="Head1">
 <title> 使用RenderContents方法實現控件呈現</title>
 </head>
 <body>
 <form name="form1" method="post" action="Default.aspx" id="form1">
 <div>
 <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTEyNTQwNjQyMDJkZOlJ3PyMGs2hmzn9MU6Ogt9V+5ag" />
 </div>
 <div>
 <a id="CustomerControl" href="mailto:[email protected]" style="color:Blue;font-size:Small;font-weight:bold;">我的郵箱地址</a>
 </div>
 </form>
 </body>
 </html>





    通過觀察以上代碼可知,自定義服務器控件RenderContentsControl實際呈現的結果是粗體所示部分的代碼,其最終呈現爲一個表示超鏈接 的<a>標記,其中包括href、Style和文本等屬性值。它們的值與Default.aspx文件源代碼 中,RenderContentsControl控件的屬性設置有着密切關係。例如,Email屬性值最終呈現爲href屬性值等等。讀者可自行對照查 看,這對於理解控件呈現很有益處。

   小結

   本文主要介紹了WebControl類的一些基本知識,並且利用這些 基本知識創建了一個簡單的控件呈現實例。實際上,從中讀者應該能夠總結出來,創建繼承自WebControl基類的自定義服務器控件,其中最爲重要的重寫 RenderContents方法。記住這一點是非常重要的。至此,筆者已經利用兩篇文章來介紹實現控件呈現的方法。在以後的文章中,將介紹有關爲控件實 現屬性等方面的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章