在ASP.NET網站上使用UserControl是常見的一種使用方法,不過Clark以往比較少在User Control上使用到Template的方法。前陣子遇到一個需求:要將所有的Grid的下方加上不同的廣告;由於這些Grid統一都是使用了一個GridControl 的UserControl,而廣告加的位置正好就在Grid和分頁器的中間。一遇到這種情況時,在還不知道Template的作法時,直覺式的用法可能就是在UserControl中加一個MultiView的ServerControl,再利用外部帶參數進入UserControl來決定要顯示什麼廣告。這樣的方式的確可以達到功能,但是每次一改廣告,就要去改動這個GridControl,似乎不是長久之計,也不是良好的設計方法。
但是一想到Panel or Repeater這種Server Control明明可以使用<xxxTemplte/>的方法來從元件外部指定內部的Control 或HTML,那一定有種方法可以讓UserControl也達到這種功能囉!?沒錯!只要宣告 ITemplate 型別的屬性就可以讓UserControl多了指定內部Control的功能,再加上實作部份程式碼就能夠將外部指定的元素Render出來。
MyBox.ascx:
<%@ Control Language="C#" ClassName="MyBox" %>
<script runat="server">1:2: public String Title { get; set; }3:4: private ITemplate _contentTemplate = null;5: /// <summary>6: /// 宣告ContentTemplate,供外部指定(e.g.<ContentTemplate>xxxx</ContentTemplate>)7: /// </summary>8: [TemplateContainer(typeof(ControlContainer))]9: [PersistenceMode(PersistenceMode.InnerProperty)]10: [TemplateInstance(TemplateInstance.Single)]11: /* TemplateInstance.Single 表示Template只實例化一次,12: * 此時才可以用ID直接指定Template中的Control, 否則 Compile 會失敗 */13: public ITemplate ContentTemplate14: {15: get16: {17: return _contentTemplate;18: }19: set20: {21: _contentTemplate = value;22: }23: }24:25: void Page_Init()26: {27: ///一旦外部有指定<ContentTemplate>內容則將內容實例化到Container中28: ///再加入PlaceHolder中29: if (_contentTemplate != null)30: {31: ControlContainer container = new ControlContainer();32: _contentTemplate.InstantiateIn(container);33: placeHolder1.Controls.Add(container);34: }35: }36:37: public class ControlContainer : Control, INamingContainer38: {39: public ControlContainer()40: {41: }42: }43:</script>
<fieldset>
<legend><% 1: =Title %></legend>
<asp:PlaceHolder ID="placeHolder1" runat="server" />
</fieldset>
Demo.aspx
<%@ Page Language="C#" %>
<%@ Register Src="~/MyBox.ascx" TagName="MyBox" TagPrefix="My" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">1:2: protected void OnbtnSearch_Click(object sender, EventArgs e)3: {4: txtSearch.Text = DateTime.Now.ToString();5: }</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>demo templated User Control</title>
<style type="text/css">
fieldset
{
border: 2px;
border-style:solid;
border-color: Black;
padding: 2px;
width: 250px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div class="leftBlock">
<My:MyBox ID="mybox1" Title="SearchBox" runat="server">
<ContentTemplate>
<!-- 測試Template中的Server Control是否可用 -->
<asp:TextBox ID="txtSearch" runat="server" />
<asp:Button ID="btnSearch" runat="server" Text="送出" OnClick="OnbtnSearch_Click" />
</ContentTemplate>
</My:MyBox>
</div>
</form>
</body>
</html>
執行結果:
當然,Template的用法不僅僅這麼簡單,當然也可以做到類似Repleater這樣的Server Control的功能。不過在這樣的實作方式中,已經可以解決Clark的問題了,如此一來只要在原來的GridControl上加上該功能,廣告就可以一率從外部指定,這樣一來元件就會更加具有彈性!
更多說明請參考MSDN 上的範例:How to: Create Templated ASP.NET User Controls
※此範例和MSDN上的範例在VS2008上均有無法在Design Mode中檢視UserControl的問題 (Error Creating Control – xxxx Type 'System.Web.UI.UserControl' does not have a public property named 'ContentTemplate' ),這個問題目前Clark還未找到解法,但是不影響執行的結果,若有網友有解決方式也請不吝賜教,造福大家一下~