Click here to Skip to main content
Licence CPOL
First Posted 24 May 2009
Views 6,592
Downloads 33
Bookmarked 10 times

An alternative way of templating controls in ASP.NET

By | 24 May 2009 | Article
Using a custom base template control to manipulate control rendering.

CustomReplaceDemo

Introduction

This article explains using an alternative approach to control templates with the ability to replace certain string values at runtime.

Background

I required some way of using templates that can support both ASP.NET controls and custom HTML replace. Remember the [Key] to "Value" replacement strings from classic ASP? I find it quite useful when dealing with inline JavaScript or CSS style classes, for example. We can also use reflection to automatically loop trough all the properties of an object and replace them on our template.

There were also some other features I needed to implement, so I decided to go for a base class.

Using the code

First, we start off by creating a base class which all our template controls will inherit from.

Remember that your custom base class must inherit at least from the Control class or the UserControl class, which depends on what you will use the control for. In our case, we will inherit from a UserControl since we want to be able to dynamically load our template control at runtime.

The next step is to create some containers for our replace object.

// 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();

We create two members that will hold all our replace objects and strings we want to get replaced. In the case of an object, we will be replacing its public properties' values, and in the case of a string, the string value itself will get replaced. How exactly it works will be discussed later.

The actual magic happens when the control is being rendered, so we have to override the Render method of our base class.

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);
    }
}

If there are no replace objects or strings in our collections, we render the content directly to the output stream.

In case there is at least one object or string waiting to be replaced, we render the control to a StringBuilder instead of directly rendering to the output stream. This gives us the chance to manipulate the rendered control's HTML before sending it to the output stream.

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);
    }
}

As mentioned in the background part already, if you remember using this technique in ASP, I'm sure you also remember writing a lot of Replace(html, "[key]", "val") lines to replace public properties for classes. Have no fear, Reflection is here :)

Using Reflection, we can loop trough all the properties and get the value of each one at runtime.

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());
            }
        }
    }
}

Fairly simple. We loop trough our replace objects collection, and using Reflection, we get a collection of properties that the object exposes. The property name is used as the key that will be replaced on the template with the property value. You could extend this sample with a recursive call that would also loop through all sub objects; keep performance penalty in mind.

Replacing our collection of custom strings is even easier. A simple loop with a call to the Replace method will do the trick.

if (_ReplaceStrings.Count > 0)
{
    for (int i = 0; i < _ReplaceStrings.Count; i++)
    {
        html = html.Replace("[" + _ReplaceStrings.Keys[i] + 
                            "]", _ReplaceStrings[i]);
    }
}

Handling server controls

We can handle server controls using the FindControl method. Because some templates can include a certain server control and some don't, I have implemented a custom variant of the FindControl method, which instead of returning null when the control is not found, returns a new instance of the control. Manipulating instances of these "null" controls does not affect the rendered content since they do not exist on the page. This way, checking nulls for each control on the template is not required and saves us a lot of time.

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));
}

As I found this really useful, I decided to implement it as an extender method so it could be used on all ASP.NET controls that inherit from the Control base class.

Preparing a template

This is a simple template example which enables you to combine both server controls and replace strings.

<%@ 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;" />

Remember that all your templates have to inherit from you template base class.

Quick example

Put a Repeater control on your ASPX page:

<asp:Repeater ID="rItems" runat="server">
    <ItemTemplate>
    </ItemTemplate>
</asp:Repeater>

Bind any data you want to the Repeater control. In this example, it's a basic product class that holds some product data like Article ID, Title, and Price.

When the data is bound, we load the template for each repeater item, just like loading a regular user control.

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);
    }
}

Conclusion

I am already using this approach in a few projects, and they seem to work pretty well. Of course, the release version should look a bit different than the demo project, like disabling the app restart when a template is changed, so you can edit templates on the fly...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dejan Bozic

Web Developer
Dejan Bozic IT
Slovenia Slovenia

Member

I like working on things related to the internet. I struggle to get web applications closer to desktop applications and have a passion for good application - website design.
 
Over the past years I have been working on multiple e-commerce, intranet, IP telephony and data integration projects.



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 24 May 2009
Article Copyright 2009 by Dejan Bozic
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid