首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET 伺服器控件開發基礎 (14):控件建構器 ControlBuilder 深度解析

     編輯:關於ASP.NET

前言:為什麼 Wizard 需要 WizardSteps 包裹?

ASP.NET 控件標記語法看似直觀,但背後有嚴格的解析規則。為什麼 <asp:Wizard> 必須包裹 <WizardSteps>?為什麼 <asp:Label> 可直接包含文字,但 <asp:TextBox> 不能包含其他控件?答案在於 ControlBuilder - 控制標記解析行為的核心機制。

ControlBuilder 核心職責


public abstract class ControlBuilder
{
    // 1. 取得子控件型別 (根據標籤名稱)
    public virtual Type GetChildControlType(string tagName, IDictionary attributes) { ... }
    
    // 2. 判斷是否允許空白內容
    public virtual bool AllowWhitespaceLiterals() { return true; }
    
    // 3. 處理巢狀標籤內容
    public virtual void AppendLiteralString(string text) { ... }
    public virtual void AppendSubBuilder(ControlBuilder subBuilder) { ... }
    
    // 4. 建立控件實例
    public virtual Control BuildObject() { ... }
    
    // 5. 初始化控件
    public virtual void Init(ControlBuilder parentBuilder, TemplateParser parser, 
        string tagName, IDictionary attributes, int line, string sourceFile, int column) { ... }
}

實戰:Wizard 控件的 ControlBuilder

問題重現




    
        內容 1
        內容 2
    




    內容 1
    內容 2

錯誤訊息:型別 'System.Web.UI.WebControls.WizardStep' 的子控件不允許

WizardControlBuilder 實作


public class WizardControlBuilder : ControlBuilder
{
    // 1. 只允許 WizardSteps 作為直接子標籤
    public override Type GetChildControlType(string tagName, IDictionary attributes)
    {
        if (tagName == "WizardSteps")
            return typeof(WizardStepCollection); // 特殊集合類別
        
        return null; // 拒絕其他直接子標籤
    }
    
    // 2. 不允許直接文字內容
    public override bool AllowWhitespaceLiterals() => false;
    
    public override void AppendLiteralString(string text)
    {
        if (!string.IsNullOrWhiteSpace(text))
            throw new HttpException("Wizard 控件不允許直接文字內容,請放入 WizardSteps 中。");
    }
}

// WizardSteps 的 ControlBuilder
public class WizardStepCollectionBuilder : ControlBuilder
{
    public override Type GetChildControlType(string tagName, IDictionary attributes)
    {
        if (tagName == "asp:WizardStep" || tagName == "WizardStep")
            return typeof(WizardStep);
        
        return null; // 只允許 WizardStep
    }
}

控件類別套用 ControlBuilder


[ControlBuilder(typeof(WizardControlBuilder))]
public class Wizard : CompositeControl
{
    [PersistenceMode(PersistenceMode.InnerProperty)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public WizardStepCollection WizardSteps { get; } = new WizardStepCollection();
    
    // ...
}

[ControlBuilder(typeof(WizardStepCollectionBuilder))]
public class WizardStepCollection : CollectionBase { ... }

常見 ControlBuilder 類型與應用場景

ControlBuilder 類型適用控件行為特徵
ControlBuilder (預設)一般容器控件 (Panel, PlaceHolder)允許任意子控件、允許文字內容
LiteralTextControlBuilderLabel, Literal, ListItem只允許文字內容,忽略巢狀控件標籤
CollectionBuilderBase集合屬性 (Columns, Items, Steps)只允許特定型別子控件,自動加入集合
自訂 ControlBuilderWizard, Menu, TreeView, TabContainer嚴格限制子標籤型別、結構、內容

實作自訂集合屬性 ControlBuilder

需求:TabContainer 僅允許 TabPanel 子標籤


// 1. 定義集合類別
[ControlBuilder(typeof(TabPanelCollectionBuilder))]
public class TabPanelCollection : CollectionBase
{
    public TabPanel this[int index] => (TabPanel)List[index];
    public void Add(TabPanel panel) => List.Add(panel);
    public void Remove(TabPanel panel) => List.Remove(panel);
}

// 2. 實作 ControlBuilder
public class TabPanelCollectionBuilder : CollectionBuilderBase
{
    public override Type GetChildControlType(string tagName, IDictionary attributes)
    {
        if (string.Equals(tagName, "TabPanel", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(tagName, "asp:TabPanel", StringComparison.OrdinalIgnoreCase))
            return typeof(TabPanel);
        
        return null; // 拒絕其他標籤
    }
}

// 3. 控件套用
[ControlBuilder(typeof(TabContainerControlBuilder))]
public class TabContainer : CompositeControl
{
    [PersistenceMode(PersistenceMode.InnerProperty)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Browsable(false)]
    public TabPanelCollection Tabs { get; } = new TabPanelCollection();
    
    // TabContainer 本身的 ControlBuilder
    public class TabContainerControlBuilder : ControlBuilder
    {
        public override Type GetChildControlType(string tagName, IDictionary attributes)
        {
            if (tagName == "Tabs") return typeof(TabPanelCollection);
            return null;
        }
        
        public override bool AllowWhitespaceLiterals() => false;
    }
}

標記解析流程深度解析

  1. 頁面剖析器 讀取 .aspx 檔案,建立 TemplateParser
  2. 遇到伺服器標籤 (如 <cc:TabContainer>),查找其 ControlBuilder 型別 (透過 ControlBuilderAttribute)
  3. 建立父 ControlBuilder 實例,呼叫 Init 初始化
  4. 遇到巢狀標籤 (如 <Tabs>),呼叫父 Builder 的 GetChildControlType 取得子控件型別
  5. 建立子 ControlBuilder,遞迴處理巢狀結構
  6. 文字內容 透過 AppendLiteralString 處理
  7. 標籤結束 呼叫 BuildObject 建立控件實例,設定屬性、加入集合
  8. 根控件建立完成,加入頁面控件樹

進階:動態控制解析行為


public class SmartContainerBuilder : ControlBuilder
{
    public override Type GetChildControlType(string tagName, IDictionary attributes)
    {
        // 根據屬性動態決定允許的子控件
        string mode = attributes["Mode"] as string;
        
        switch (mode)
        {
            case "Form": return typeof(FormField);
            case "List": return typeof(ListItem);
            case "Grid": return typeof(GridColumn);
            default: return base.GetChildControlType(tagName, attributes);
        }
    }
    
    public override void AppendSubBuilder(ControlBuilder subBuilder)
    {
        // 攔截子控件建立,注入額外初始化邏輯
        if (subBuilder.ControlType == typeof(FormField))
        {
            // 設定預設驗證群組等
        }
        base.AppendSubBuilder(subBuilder);
    }
}

設計時支援:ControlBuilder 與智慧標籤


public class TabContainerDesigner : ControlDesigner
{
    public override DesignerActionListCollection ActionLists
    {
        get
        {
            DesignerActionListCollection lists = new DesignerActionListCollection();
            lists.Add(new TabContainerActionList(this));
            return lists;
        }
    }
    
    // 設計時預覽:模擬 ControlBuilder 邏輯渲染
    public override string GetDesignTimeHtml()
    {
        TabContainer control = (TabContainer)Component;
        control.EnsureChildControls();
        return base.GetDesignTimeHtml();
    }
}

public class TabContainerActionList : DesignerActionList
{
    public TabContainerActionList(ComponentDesigner designer) : base(designer) { }
    
    public override DesignerActionItemCollection GetSortedActionItems()
    {
        return new DesignerActionItemCollection
        {
            new DesignerActionMethodItem(this, "AddTab", "新增分頁"),
            new DesignerActionMethodItem(this, "RemoveTab", "移除分頁"),
            new DesignerActionPropertyItem("ActiveTabIndex", "啟用分頁索引"),
        };
    }
    
    public void AddTab() { /* 透過 ControlBuilder 邏輯新增 TabPanel */ }
    public void RemoveTab() { /* ... */ }
}

總結

ControlBuilder 是 ASP.NET 標記語言的語法分析器,決定了:

  1. 控件允許哪些子標籤 (GetChildControlType)
  2. 控件允許直接文字內容 (AllowWhitespaceLiterals/AppendLiteralString)
  3. 巢狀標籤如何轉換為控件實例與集合 (AppendSubBuilder/BuildObject)
  4. 設計時智慧標籤如何操作控件結構

掌握 ControlBuilder,即可設計出語法嚴謹、使用直觀、設計時體驗優異的專業伺服器控件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved