ASP.NET 控件模板的另一种方法






4.67/5 (4投票s)
使用自定义基模板控件来操作控件的渲染。
引言
本文介绍了一种替代控件模板的方法,该方法能够在运行时替换特定的字符串值。
背景
我需要一种能够同时支持 ASP.NET 控件和自定义 HTML 替换的模板。还记得经典 ASP 中的 "[Key]" 到 "Value" 替换字符串吗?我认为它在处理内联 JavaScript 或 CSS 样式类时非常有用。我们还可以使用反射自动遍历对象的全部属性,并在我们的模板中替换它们。
还有一些其他功能我也需要实现,所以我决定采用一个基类。
使用代码
首先,我们创建一个所有模板控件都将继承的基类。
请记住,您的自定义基类至少必须继承自 Control
类或 UserControl
类,具体取决于您将如何使用该控件。在本例中,我们将继承自 UserControl
,因为我们希望能够在运行时动态加载模板控件。
下一步是为我们的替换对象创建一些容器。
// Collection of custom objects that will be
// used to replace variables with actual values.
private List<object> _ReplaceObjects = new List<object>();
// Collection of key values that will be
// used to replace variables with actual values.
private NameValueCollection _ReplaceStrings = new NameValueCollection();
我们创建两个成员,它们将保存所有要替换的替换对象和字符串。对于对象,我们将替换其公共属性的值;对于字符串,字符串值本身将被替换。具体工作原理稍后讨论。
实际的魔力发生在控件渲染时,所以我们必须重写基类的 Render
方法。
protected override void Render(HtmlTextWriter writer)
{
// Check if any replace objects have been added
if (ReplaceObjects.Count > 0 || _ReplaceStrings.Count > 0)
{
//
// Additional code, see bellow...
//
}
else
{
// Normal rendering
base.Render(writer);
}
}
如果我们的集合中没有替换对象或字符串,我们将直接将内容渲染到输出流。
如果至少有一个对象或字符串等待被替换,我们将控件渲染到 StringBuilder
,而不是直接渲染到输出流。这使我们有机会在将渲染的控件的 HTML 发送到输出流之前对其进行操作。
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
// Render HTML to stringbuilder
base.Render(tw);
// String.Replace method should perform faster than
// using StringBuilder.Replace.
string html = sb.ToString();
//
// Additional code, see bellow...
//
// Write replaced HTML to output stream
writer.Write(html);
}
}
正如背景部分已经提到的,如果您还记得在 ASP 中使用此技术,我相信您也还记得编写了大量的 Replace(html, "[key]", "val")
行来替换类的公共属性。不用担心,反射来了 :)
使用反射,我们可以遍历所有属性,并在运行时获取每个属性的值。
for (int i = 0; i < ReplaceObjects.Count; i++)
{
// Loop trough all object's properties
foreach (PropertyInfo prop in ReplaceObjects[i].GetType().GetProperties())
{
if (ReplaceObjects[i] != null)
{
object val = prop.GetValue(ReplaceObjects[i], null);
if (val == null)
{
html = html.Replace("[" + prop.Name + "]", "");
}
else
{
html = html.Replace("[" + prop.Name + "]", val.ToString());
}
}
}
}
相当简单。我们遍历替换对象集合,并使用反射获取对象公开的属性集合。属性名用作将在模板中被属性值替换的键。您可以扩展此示例,通过递归调用来遍历所有子对象;请注意性能开销。
替换我们的自定义字符串集合甚至更容易。一个简单的循环加上对 Replace
方法的调用就足以完成任务。
if (_ReplaceStrings.Count > 0)
{
for (int i = 0; i < _ReplaceStrings.Count; i++)
{
html = html.Replace("[" + _ReplaceStrings.Keys[i] +
"]", _ReplaceStrings[i]);
}
}
处理服务器控件
我们可以使用 FindControl
方法来处理服务器控件。由于有些模板可能包含某些服务器控件,而有些则不包含,所以我实现了一个 FindControl
方法的自定义变体。该方法在找不到控件时不会返回 null
,而是返回控件的新实例。操作这些“null”控件的实例不会影响渲染内容,因为它们不存在于页面上。这样,就不需要检查模板中每个控件的 null
值,从而节省了大量时间。
public static T FindControl<T>(this Control control, string id) where T : Control
{
// Use normal FindControl method to get the control
Control _control = control.FindControl(id);
// If control was found and is of the correct type we return it
if (_control != null && _control is T)
{
return (T)_control;
}
// Use reflection to create a new instance of the control
return (T)Activator.CreateInstance(typeof(T));
}
我觉得这非常有价值,所以我决定将其实现为扩展方法,以便可以将其用于所有继承自 Control
基类的 ASP.NET 控件。
准备模板
这是一个简单的模板示例,它使您能够同时结合服务器控件和替换字符串。
<%@ Control Language="C#" AutoEventWireup="true"
Inherits="CustomReplaceDemo.CustomReplaceBase" %>
<h2>
[Title]
</h2>
(ID: [ArticleID])
<br />
Price: [Price] EUR
<asp:Button runat="server" ID="bDetils" Text="Details"
OnClientClick="javascript:alert('[Title] costs [Price] EUR.');return false;" />
请记住,所有模板都必须继承自您的模板基类。
快速示例
将 Repeater
控件放在您的 ASPX 页面上
<asp:Repeater ID="rItems" runat="server">
<ItemTemplate>
</ItemTemplate>
</asp:Repeater>
将您想要的任何数据绑定到 Repeater
控件。在本例中,它是一个基本的产品类,包含一些产品数据,如 Article ID、Title 和 Price。
数据绑定后,我们就像加载常规用户控件一样,为每个 Repeater 项加载模板。
void rItems_ItemDataBound(object sender,
System.Web.UI.WebControls.RepeaterItemEventArgs e) {
if (e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
Article article = (Article)e.Item.DataItem;
// Load template
CustomReplaceBase articleTemplate = (CustomReplaceBase)
LoadControl("Templates/ArticleTemplate.ascx");
// Add custom replace object
articleTemplate.ReplaceObjects.Add(article);
// Add custom replace string
articleTemplate.ReplaceStrings["Number"] = _Count++.ToString();
// Add template to list item
e.Item.Controls.Add(articleTemplate);
// Bind template controls...
// No logic is implemented on the template itself.
Button bDetils = articleTemplate.FindControl<Button>("bDetils");
// You can now add event listeners or manipulate
// controls on the temaplte...
// bDetils.Text = "My new text";
// bDetils.Click += new EventHandler(bDetils_Click);
}
}
回发处理
回发控件必须订阅基模板中的 Postback 事件处理程序,该处理程序会将请求转发到下面的代码。
<asp:Button runat="server" ID="bPostback" Text="Postback" OnClick="Postback" />
有两种方法可以订阅模板上的回发事件。首先,我们可以订阅模板上的 OnPostback 事件,或者其次,为托管模板的页面实现一个自定义基类。
// Subscribe to the postback forwarding event
articleTemplate.OnPostback += new EventHandler(articleTemplate_OnPostback);
// Postback, outoevent wireup
public override void OnPostback(object sender, EventArgs e)
{
...
}
结论
我已经将此方法用于几个项目,它们运行得相当不错。当然,发布版本应该看起来与演示项目有所不同,例如禁用在模板更改时重启应用程序,这样您就可以即时编辑模板……