使用.NET一年多了,前不久开始迷上了.NET的控件开发,发觉这真是一个表现创造力的工作:)。
前不久应公司要求写一个在公司网站全站通用的留言本,因为公司旗下网站(www.163888.net)内容比较庞杂,而且时常会出现各种活动,留言本的使用频率非常高,而原先的方法是每次活动都从新开发一个留言本,实在是非常不科学的方式,而实际上每个留言本的逻辑处理基本上是一致的,因此才有了开发一个全站通用的留言本的想法。
任务到手之后,思考实现时,第一个想到了动态调用.ascx文件的方式来写一个复合控件,但是经过再三考虑之后,发觉这个方法存在不少问题。
虽然它达到了同一逻辑结构,灵活换肤的需求,但是正如我先前所说留言本在本站使用频率很高,这样每次使用都会产生1-2个.ascx文件,感觉上有点累赘。
经过再三考虑,无意中想到了Repeater这个控件,它使用ItemTemplate和AlternatingItemTemplate两个属性灵活包含模板而不产生.ascx文件,这不正是我所需要的吗?我何不写一个控件内部绑定数据的类似Repeater的控件?
有了这个想法之后,立刻使用Reflector打开了System.Web.dll找到了Repeater控件,参考着完成了下面的GuestBook控件。
1
using
System;
2
using
System.Web.UI;
3
using
System.Web.UI.WebControls;
4
using
System.ComponentModel;
5
using
System.Collections;
6
using
SunBird.Controls;
7
using
SunBird.Components;
8
9
namespace
SunBird.GuestBookControl
10
{
11 [ParseChildren(true), PersistChildren(false)]
12 public class GuestBook : Control , INamingContainer
13 {
14 ITemplate itemTemplate;
15 [DefaultValue((string)null), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GuestBookItem)), Browsable(false)]
16 public ITemplate ItemTemplate
17 {
18 get { return itemTemplate; }
19 set { itemTemplate = value; }
20 }
21
22 ITemplate alternatingItemTemplate;
23 [DefaultValue((string)null), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GuestBookItem)), Browsable(false)]
24 public ITemplate AlternatingItemTemplate
25 {
26 get { return alternatingItemTemplate; }
27 set { alternatingItemTemplate = value; }
28 }
29
30 ITemplate pagerTemplate;
31 [DefaultValue((string)null), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GuestBookItem)), Browsable(false)]
32 public ITemplate PagerTemplate
33 {
34 get { return pagerTemplate; }
35 set { pagerTemplate = value; }
36 }
37
38 object dataSource;
39 [Browsable(false)]
40 public object DataSource
41 {
42 get { return dataSource; }
43 set { dataSource = value; }
44 }
45
46 int type;
47 public int Type
48 {
49 get { return type; }
50 set { type = value; }
51 }
52
53 int markup;
54 public int Markup
55 {
56 get { return markup; }
57 set { markup = value; }
58 }
59
60 int top;
61 public int Top
62 {
63 get { return top; }
64 set { top = value; }
65 }
66
67 int pageSize;
68 public int PageSize
69 {
70 set { pageSize = value; }
71 get { return pageSize <= 0 ? 10 : pageSize;}
72 }
73
74 int superUser;
75 public int SuperUser
76 {
77 get
78 {
79 if (superUser <= 0)
80 return -1;
81 return superUser;
82 }
83 set { superUser = value; }
84 }
85
86
87 protected override void OnLoad(EventArgs e)
88 {
89 base.OnLoad (e);
90 if (!Page.IsPostBack)
91 {
92 DataBind();
93 }
94 }
95
96 DataBind#region DataBind
97 int totalRecords;
98 public override void DataBind()
99 {
100 if (type == 0) {
101 throw new ArgumentException("为指定读取类型(Type)。");
102 }
103 if (markup == 0) {
104 throw new ArgumentException("未指定读取标识(Markup)。");
105 }
106
107 int pageIndex = Globals.SafeInt(UContext.Current.QueryString["PageIndex"], 1);
108
109 ArrayList posts = GuestBookDataProvider.Instance().GetPosts(type, markup, top, pageIndex, PageSize, out totalRecords);
110 DataSource = posts;
111
112 base.DataBind ();
113 }
114 #endregion
115
116 OnDataBinding#region OnDataBinding
117 protected override void OnDataBinding(EventArgs e)
118 {
119 CreateDataItemTemplate();
120 CreatePagerTemplate();
121 base.OnDataBinding(e);
122 }
123 #endregion
124
125 CreatePagerTemplate#region CreatePagerTemplate
126 private void CreatePagerTemplate()
127 {
128 if (pagerTemplate != null && dataSource != null && top == 0)
129 {
130 GuestBookItem item = new GuestBookItem(-1, GuestBookItemType.Pager);
131 pagerTemplate.InstantiateIn(item);
132 Pager p = new Pager();
133 p.PageSize = PageSize;
134 p.TotalRecords = totalRecords;
135
136 item.Controls.Add(p);
137
138 Controls.Add(item);
139 }
140 }
141 #endregion
142
143 CreateDataItemTemplate#region CreateDataItemTemplate
144 private void CreateDataItemTemplate()
145 {
146 if (itemTemplate != null)
147 {
148 if (dataSource != null)
149 {
150 int itemCount = 1;
151
152 foreach(object data in (ArrayList)dataSource)
153 {
154 ITemplate template = itemTemplate;
155 GuestBookItemType itemType = GuestBookItemType.Item;
156 if(alternatingItemTemplate != null)
157 {
158 if (itemCount % 2 > 0)
159 {
160 template = alternatingItemTemplate;
161 itemType = GuestBookItemType.AlternatingItem;
162 }
163 }
164 GuestBookItem item = new GuestBookItem(itemCount, itemType);
165
166 item.DataItem = data;
167
168 template.InstantiateIn(item);
169
170 GuestBook_ItemDataBound(item);
171
172 Controls.Add(item);
173
174 itemCount ++;
175 }
176
177 ViewState["ItemCount"] = itemCount;
178 }
179
180 ChildControlsCreated = true;
181 }
182 }
183 #endregion
184
185 GuestBook_ItemDataBound#region GuestBook_ItemDataBound
186 private void GuestBook_ItemDataBound(GuestBookItem item)
187 {
188 GBPost post = item.DataItem as GBPost;
189 if (post != null)
190 {
191 Image GBUserHead = item.FindControl("GBUserHead") as Image;
192 if (GBUserHead != null)
193 {
194 if (!Globals.IsNullorEmpty(post.UserHead))
195 GBUserHead.ImageUrl = post.UserHead;
196 else
197 GBUserHead.Visible = false;
198 }
199
200 HyperLink GBUser = item.FindControl("GBUser") as HyperLink;
201 if (GBUser != null)
202 {
203 GBUser.Text = post.UserName;
204 GBUser.NavigateUrl = "http://" + post.UserID + ".163888.net/";
205 GBUser.Target = "_blank";
206 }
207
208 Literal GBBody = item.FindControl("GBBody") as Literal;
209 if (GBBody != null)
210 {
211 GBBody.Text = post.Body;
212 Literal GBReplayBody = item.FindControl("GBReplayBody") as Literal;
213 if (GBReplayBody != null)
214 {
215 if (!Globals.IsNullorEmpty(post.ReplyBody))
216 {
217 GBReplayBody.Visible = true;
218 GBReplayBody.Text = "<font color=\"#808080\">回复</font>:" + post.ReplyBody;
219 }
220 else
221 {
222 GBReplayBody.Visible = false;
223 }
224
225 }
226 }
227
228 User user = UContext.Current.User;
229
230 LinkButton GBDelPost = item.FindControl("GBDelPost") as LinkButton;
231 if (GBDelPost != null)
232 {
233 if (user.UserID == SuperUser)
234 {
235 GBDelPost.Visible = true;
236 GBDelPost.CausesValidation = false;
237 GBDelPost.Command += new CommandEventHandler(GBDelPost_Command);
238 GBDelPost.CommandName = "DelPost";
239 GBDelPost.CommandArgument = post.ID.ToString();
240 }
241 else
242 {
243 GBDelPost.Visible = false;
244 }
245 }
246
247 HyperLink GBReplayPost = item.FindControl("GBReplayPost") as HyperLink;
248 if (GBReplayPost != null)
249 {
250 if (user.UserID == SuperUser)
251 {
252 string script = "<script language=\"javascript\">" + Environment.NewLine
253 + "function openWindow(pagePath, args, width, height) {" + Environment.NewLine
254 + " var s = '';" + Environment.NewLine
255 + " s = 'width=' + width + ',height=' + height + ',';" + Environment.NewLine
256 + " var win = window.open(pagePath, 'guestbook', s + 'resizable=no,toolbar=no,scrollbars=no,status=no,alwaysLowered=yes,');" + Environment.NewLine
257 + " win.focus();" + Environment.NewLine
258 + " win.moveTo( (screen.width - width)/2, (screen.height - height)/2 );}</script>" + Environment.NewLine;
259 GBReplayPost.Visible = true;
260 Page.RegisterClientScriptBlock("guestBook_openWindow", script);
261 GBReplayPost.NavigateUrl = "javascript:openWindow('/GuestBook/GuestBook_Replay.aspx?rid=" + post.ID + "', '', 350, 150);";
262 }
263 else
264 {
265 GBReplayPost.Visible = false;
266 }
267 }
268
269 Literal GBDate = item.FindControl("GBDate") as Literal;
270 if (GBDate != null)
271 {
272 GBDate.Text = post.DateCreated.ToShortDateString();
273 }
274
275 }
276 }
277 #endregion
278
279 GBDelPost_Command#region GBDelPost_Command
280 private void GBDelPost_Command(object sender, CommandEventArgs e)
281 {
282 string cmd = e.CommandName;
283 int postID = int.Parse(e.CommandArgument as string);
284 switch(cmd)
285 {
286 case "DelPost":
287 GuestBookDataProvider.Instance().DeletePost(postID);
288 break;
289 }
290 }
291 #endregion
292 }
293}
如果使用这样一个控件,在页面注册之后只需要使用它的ItemTemplate和AlternatingItemTemplate属性就可以加载模板了,比原先设想的动态加载.ascx文件的方式要好得多,如:
<%
@ Register TagPrefix="GB" Namespace="SunBird.GuestBookControl" Assembly="SunBird.GuestBook"
%>
<
GB:GuestBook
id
="guestBook"
runat
="server"
Type
="0"
Markup
="0"
>
<
ItemTemplate
>
模板代码
</
ItemTemplate
>
<
AlternatingItemTemplate
>
奇数行模板代码
</
AlternatingItemTemplate
>
<
PagerTemplate
/>
<!--
分页
-->
</
GB:GuestBook
>
这样就完成了该控件的定义,PagerTemplate属性是分页显示。
下面是留言本信息的发布,发不分两种:1、需要登录,2、不需要登录。
于是分别写了三个控件类:
1、GuestBookCreateBase (基类) 它定义了大部分的功能。
1
using
System;
2
using
System.Web.UI.WebControls;
3
using
SunBird.Components;
4
5
namespace
SunBird.GuestBookControl
6
{
7 public class GuestBookCreateBase : GuestBookSkinBase
8 {
9 public override void DataBind()
10 {
11 base.DataBind();
12 }
13
14 protected override void OnLoad(EventArgs e)
15 {
16 base.OnLoad(e);
17 if (!Page.IsPostBack)
18 {
19 DataBind();
20 }
21 }
22
23 AttachChildCotrols#region AttachChildCotrols
24 protected TextBox Content;
25 protected Button CreateButton;
26 protected ImageButton CreateImageButton;
27 protected override void AttachChildControls()
28 {
29 Content = (TextBox) FindControl("Content");
30 CreateButton = FindControl("CreateButton") as Button;
31 if (CreateButton == null)
32 {
33 throw new Exception("缺少必要的请求控件(Button)。");
34 }
35 CreateButton.Click += new EventHandler(CreateButton_Click);
36 }
37 #endregion
38
39 CreateButton_Click#region CreateButton_Click
40 protected virtual void CreateButton_Click(object sender, EventArgs e)
41 {
42 if (Globals.IsNullorEmpty(Content.Text))
43 {
44 Globals.OutMessage("请输入评论内容。");
45 return;
46 }
47
48 User user = UContext.Current.User;
49
50 GBPost post = new GBPost();
51 post.UserID = user.UserID;
52 post.UserName = user.UserName;
53 post.Type = Type;
54 post.Markup = Markup;
55 post.Body = HtmlScrubber.Clean(Content.Text, false, true);
56 post.DateCreated = DateTime.Now;
57
58 GuestBookDataProvider.Instance().CreatePost(post);
59
60 Uri uri = UContext.Current.CurrentUri;
61 if (uri != null)
62 {
63 Page.Response.Redirect(uri.ToString());
64 }
65 }
66 #endregion
67
68 int type;
69 public virtual int Type
70 {
71 get { return type; }
72 set { type = value; }
73 }
74
75 int markup;
76 public virtual int Markup
77 {
78 get { return markup; }
79 set { markup = value; }
80 }
81 }
82}
2、CreatePostNeedLogin : GuestBookCreateBase 需要登录,他实际上只是override了基类的CreateButton_Click方法,为该方法加入了验证登录的操作。
1
using
System;
2
using
System.Web.UI.WebControls;
3
using
SunBird.Components;
4
5
namespace
SunBird.GuestBookControl
6
{
7 public class CreatePostNeedLogin : GuestBookCreateBase
8 {
9 protected TextBox UserName;
10 protected TextBox UserPassword;
11 protected override void AttachChildControls()
12 {
13 base.AttachChildControls();
14
15 UserName = (TextBox)FindControl("UserName");
16 UserPassword = (TextBox)FindControl("UserPassword");
17 }
18
19 protected override void CreateButton_Click(object sender, EventArgs e)
20 {
21 User user = UContext.Current.User;
22 if (user.IsAnonymous)
23 {
24 if (Globals.IsNullorEmpty(UserName.Text))
25 {
26 Globals.OutMessage("请输入用户名。");
27 return;
28 }
29 if (Globals.IsNullorEmpty(UserPassword.Text))
30 {
31 Globals.OutMessage("请输入密码。");
32 return;
33 }
34
35 int userId;
36 string name = UserName.Text;
37 string password = NewBase.String.GetMd5(UserPassword.Text);
38 bool result = GuestBookDataProvider.Instance().ValidateUser(name, password, out userId);
39 if (result)
40 {
41 user.UserID = userId;
42 user.UserName = name;
43 Users.SaveUserCookie(user);
44 }
45 else
46 {
47 Globals.OutMessage("用户名或密码错误。");
48 return;
49 }
50 }
51
52
53 base.CreateButton_Click (sender, e);
54 }
55
56 }
57}
3、CreatePostNotNeedLogin : GuestBookCreateBase 不需要登录,实际上该类没有做任何操作仅仅只是为了方便其他人使用。
1
using
System;
2
using
System.Web.UI.WebControls;
3
4
namespace
SunBird.GuestBookControl
5
{
6 public class CreatePostNotNeedLogin : GuestBookCreateBase
7 {
8 protected override void AttachChildControls()
9 {
10 base.AttachChildControls();
11
12
13 }
14 }
15}
控件的核心的类差不多就这几个,完整的代码如果有兴趣,请下载附件查看。
终于找到上传附件的地方了,下面是源代码,有兴趣的朋友可以下载看看,如果你有更好的方法请一定告诉我。
GuestBookControl源代码