65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (4投票s)

2009年5月24日

CPOL

4分钟阅读

viewsIcon

32165

downloadIcon

293

使用自定义基模板控件来操作控件的渲染。

CustomReplaceDemo

引言

本文介绍了一种替代控件模板的方法,该方法能够在运行时替换特定的字符串值。

背景

我需要一种能够同时支持 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)
{
    ...
}

结论 

我已经将此方法用于几个项目,它们运行得相当不错。当然,发布版本应该看起来与演示项目有所不同,例如禁用在模板更改时重启应用程序,这样您就可以即时编辑模板…… 

 

 

© . All rights reserved.