ASP.NET 內建控件 (Repeater, DataList, GridView, FormView, LoginView 等) 皆採用模板架構,將 UI 結構外部化,讓開發者以宣告式標籤定義呈現邏輯。掌握 ITemplate 介面與 InstantiateIn 方法,是開發專業級伺服器控件的核心技能。
Control 或 WebControlINamingContainer 確保命名唯一ITemplate 型別屬性CreateChildControls 中實例化模板DataBinding 事件進行資料繫結
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;
}
}
RepeaterItem, GridViewRow)Eval/Bind IntelliSense
public class MyItem : Control, INamingContainer
{
// 容器可定義資料項屬性,供模板存取
public object DataItem { get; set; }
public int ItemIndex { get; set; }
}
public class SimpleTemplateControl : WebControl, INamingContainer
{
private ITemplate _itemTemplate;
[Browsable(false)]
[TemplateContainer(typeof(MyItem))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ItemTemplate
{
get => _itemTemplate;
set => _itemTemplate = value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
// 建立單一項目容器
MyItem container = new MyItem
{
DataItem = "靜態測試資料",
ItemIndex = 0
};
// 實例化模板到容器
ItemTemplate?.InstantiateIn(container);
// 將容器加入控件樹
Controls.Add(container);
ChildControlsCreated = true;
}
<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;
}
public interface IDataItemContainer : INamingContainer
{
object DataItem { get; }
int DataItemIndex { get; }
int DisplayIndex { get; }
}
實作此介面,模板內才能使用 Container.DataItem、Container.DataItemIndex,並啟用 Eval("FieldName")/Bind("FieldName") 語法。
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;
}
}
[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) 模式:
ITemplate 屬性,標記 TemplateContainer、PersistenceMode.InnerPropertyINamingContainer (且通常 IDataItemContainer) 的容器類別CreateChildControls 中遍歷資料源,建立容器、實例化模板、DataBind、加入控件樹Header/Item/Alternating/Separator/Footer) 實現豐富 UI掌握此模式,即可開發出具備設計時支援、資料繫結能力、高度客製化的專業模板控件。