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

ASP.NET 伺服器控件開發基礎 (17):自訂模板控件基礎與 ITemplate 實作

     編輯:關於ASP.NET

前言:模板控件是複雜控件的基石

ASP.NET 內建控件 (Repeater, DataList, GridView, FormView, LoginView 等) 皆採用模板架構,將 UI 結構外部化,讓開發者以宣告式標籤定義呈現邏輯。掌握 ITemplate 介面與 InstantiateIn 方法,是開發專業級伺服器控件的核心技能。

內建模板控件概覽

ASP.NET 內建模板控件

模板控件共同特徵

  • 繼承 Control 或 WebControl
  • 實作 INamingContainer 確保命名唯一
  • 定義多個 ITemplate 型別屬性
  • 在 CreateChildControls 中實例化模板
  • 支援 DataBinding 事件進行資料繫結

ITemplate 介面核心


namespace System.Web.UI
{
    public interface ITemplate
    {
        /// 
        /// 在指定容器中實例化模板內容
        /// 
        /// 模板容器控件 (實作 INamingContainer)
        void InstantiateIn(Control container);
    }
}

模板屬性宣告標準模式


public class MyTemplateControl : WebControl, INamingContainer
{
    // 1. 私有欄位
    private ITemplate _itemTemplate;
    
    // 2. 公開屬性 + 設計時屬性
    [Browsable(false)]                    // 不在屬性視窗顯示 (由設計器處理)
    [TemplateContainer(typeof(MyItem))]   // 指定模板容器型別,啟用 IntelliSense
    [PersistenceMode(PersistenceMode.InnerProperty)] // 以內屬性標籤持久化
    public ITemplate ItemTemplate
    {
        get => _itemTemplate;
        set => _itemTemplate = value;
    }
}

TemplateContainerAttribute 關鍵作用

  • 告訴設計器模板內的容器型別 (如 RepeaterItem, GridViewRow)
  • 啟用 Visual Studio 原始碼編輯器的 Eval/Bind IntelliSense
  • 編譯期驗證模板內資料繫結運算式的屬性存取

靜態模板實作:最簡模板控件

步驟 1:定義模板容器


public class MyItem : Control, INamingContainer
{
    // 容器可定義資料項屬性,供模板存取
    public object DataItem { get; set; }
    public int ItemIndex { get; set; }
}

步驟 2:建立控件與模板屬性


public class SimpleTemplateControl : WebControl, INamingContainer
{
    private ITemplate _itemTemplate;
    
    [Browsable(false)]
    [TemplateContainer(typeof(MyItem))]
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public ITemplate ItemTemplate
    {
        get => _itemTemplate;
        set => _itemTemplate = value;
    }
}

步驟 3:在 CreateChildControls 實例化模板


protected override void CreateChildControls()
{
    Controls.Clear();
    
    // 建立單一項目容器
    MyItem container = new MyItem 
    { 
        DataItem = "靜態測試資料", 
        ItemIndex = 0 
    };
    
    // 實例化模板到容器
    ItemTemplate?.InstantiateIn(container);
    
    // 將容器加入控件樹
    Controls.Add(container);
    
    ChildControlsCreated = true;
}

步驟 4:頁面使用


<cc:SimpleTemplateControl ID="TemplateCtrl1" runat="server">
    <ItemTemplate>
        <div class="item">
            <h3>項目內容</h3>
            <p><%# Container.DataItem %></p>
            <span>索引: <%# Container.ItemIndex %></span>
        </div>
    </ItemTemplate>
</cc:SimpleTemplateControl>

動態資料繫結模板控件

核心差異:資料驅動模板實例化


public class DataBoundTemplateControl : WebControl, INamingContainer
{
    private ITemplate _itemTemplate;
    private object _dataSource;
    
    [Browsable(false)]
    [TemplateContainer(typeof(DataBoundItem))]
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public ITemplate ItemTemplate
    {
        get => _itemTemplate;
        set => _itemTemplate = value;
    }
    
    public object DataSource
    {
        get => _dataSource;
        set { _dataSource = value; }
    }
    
    protected override void CreateChildControls()
    {
        Controls.Clear();
        
        IEnumerable data = ResolveDataSource(); // 解析 DataSource
        if (data != null)
        {
            int index = 0;
            foreach (object item in data)
            {
                DataBoundItem container = new DataBoundItem 
                { 
                    DataItem = item, 
                    ItemIndex = index 
                };
                
                ItemTemplate?.InstantiateIn(container);
                container.DataBind(); // 觸發模板內控件 DataBinding 事件
                
                Controls.Add(container);
                index++;
            }
        }
        ChildControlsCreated = true;
    }
    
    protected virtual IEnumerable ResolveDataSource()
    {
        // 同前篇 ResolveDataSource 邏輯
        return _dataSource as IEnumerable;
    }
}

public class DataBoundItem : Control, INamingContainer, IDataItemContainer
{
    public object DataItem { get; set; }
    public int ItemIndex { get; set; }
    public int DataItemIndex => ItemIndex;
    public int DisplayIndex => ItemIndex;
}

IDataItemContainer 介面關鍵


public interface IDataItemContainer : INamingContainer
{
    object DataItem { get; }
    int DataItemIndex { get; }
    int DisplayIndex { get; }
}

實作此介面,模板內才能使用 Container.DataItem、Container.DataItemIndex,並啟用 Eval("FieldName")/Bind("FieldName") 語法。

多模板支援:HeaderTemplate、FooterTemplate、SeparatorTemplate


public class AdvancedTemplateControl : WebControl, INamingContainer
{
    // 模板屬性集合
    private ITemplate _headerTemplate;
    private ITemplate _itemTemplate;
    private ITemplate _alternatingItemTemplate;
    private ITemplate _separatorTemplate;
    private ITemplate _footerTemplate;
    
    // ... 屬性宣告 (同上模式) ...
    
    protected override void CreateChildControls()
    {
        Controls.Clear();
        IEnumerable data = ResolveDataSource();
        
        // 1. HeaderTemplate
        if (_headerTemplate != null)
        {
            Control header = new Control();
            _headerTemplate.InstantiateIn(header);
            Controls.Add(header);
        }
        
        // 2. 資料項目
        if (data != null)
        {
            int index = 0;
            foreach (object item in data)
            {
                // SeparatorTemplate (非第一項前)
                if (index > 0 && _separatorTemplate != null)
                {
                    Control sep = new Control();
                    _separatorTemplate.InstantiateIn(sep);
                    Controls.Add(sep);
                }
                
                // ItemTemplate / AlternatingItemTemplate
                DataBoundItem container = new DataBoundItem { DataItem = item, ItemIndex = index };
                ITemplate template = (index % 2 == 0) ? _itemTemplate : _alternatingItemTemplate;
                template?.InstantiateIn(container);
                container.DataBind();
                Controls.Add(container);
                
                index++;
            }
        }
        
        // 3. FooterTemplate
        if (_footerTemplate != null)
        {
            Control footer = new Control();
            _footerTemplate.InstantiateIn(footer);
            Controls.Add(footer);
        }
        
        ChildControlsCreated = true;
    }
}

設計時支援:讓模板在 Visual Studio 中可視化編輯


[Designer(typeof(TemplateControlDesigner))]
public class MyTemplateControl : WebControl, INamingContainer
{
    // ...
}

public class TemplateControlDesigner : ControlDesigner
{
    // 提供智慧標籤:編輯模板、切換模板
    public override DesignerActionListCollection ActionLists
    {
        get
        {
            DesignerActionListCollection lists = new DesignerActionListCollection();
            lists.Add(new TemplateEditingActionList(this));
            return lists;
        }
    }
    
    // 設計時預覽渲染
    public override string GetDesignTimeHtml()
    {
        // 回傳設計時預覽 HTML
        return "
[模板控件設計時預覽]
"; } }

常見錯誤與最佳實踐

錯誤原因解決方案
模板內容不顯示 未呼叫 InstantiateIn 或未加入 Controls 集合 確保 CreateChildControls 中正確實例化並加入
Eval 無法運作 容器未實作 IDataItemContainer 容器類別實作 IDataItemContainer 介面
設計時無 IntelliSense 缺少 TemplateContainerAttribute 加入 [TemplateContainer(typeof(YourContainer))]
Postback 後模板消失 未正確實作視圖狀態或 ChildControlsCreated 確保 ChildControlsCreated = true 且視圖狀態正常
命名衝突 容器未實作 INamingContainer 所有模板容器必須實作 INamingContainer

總結

模板控件核心在於 ITemplate.InstantiateIn(container) 模式:

  1. 定義 ITemplate 屬性,標記 TemplateContainer、PersistenceMode.InnerProperty
  2. 建立實作 INamingContainer (且通常 IDataItemContainer) 的容器類別
  3. 在 CreateChildControls 中遍歷資料源,建立容器、實例化模板、DataBind、加入控件樹
  4. 支援多模板 (Header/Item/Alternating/Separator/Footer) 實現豐富 UI

掌握此模式,即可開發出具備設計時支援、資料繫結能力、高度客製化的專業模板控件。

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