Click here to Skip to main content
15,867,141 members
Articles / Web Development / ASP.NET
Article

Truly Dynamic and Re-usable DataGrids

Rate me:
Please Sign up or sign in to vote.
4.82/5 (27 votes)
10 Oct 2005CPOL4 min read 88.7K   994   79   6
How to add DataGrid columns dynamically with ITemplate, XML and templated controls.

Introduction

The DataGrid provides web developers limitless options to display data in tabular format. Although it does provide a number of ways to display data, it does not inherently promote code and format reuse.

The article and the source code that accompanies it demonstrates the usage of a helper class called the GridHelper which builds Datagrids dynamically using XML, template files, and the ITemplate interface. The main goal is to provide a class that can be reused across many sites using your own custom template hierarchy. It also allows developers to append formatted columns to grids at run-time without recompilation.

Background

Nothing bothers me more than creating DataGrids with the same look and feel across multiple sites. I saw a need to create a mechanism that would embrace my lazy nature by removing the need to create Datagrids by hand. While CSS helps in the formatting of columns, it does not allow you to join two rows of data to create a single column or format a specific data type across multiple pages with multiple grids.

Using the code

There are three main pieces of this project:

  • TemplateLoader – Object that inherits from the Page object and hides the LoadTemplate method for loading and caching templates from a file.
  • GridFormats – Stores the grid columns from the Web.Config file. This is serialized into this object from the custom ConfigurationSectionHandler object.
  • GridHelper – Performs dynamic generation of grid columns using the TemplateLoader and GridFormats classes.

TemplateLoader

The purpose of this class is to load templates from .ascx files. LoadTemplate is an inherited method available to Page and UserControl objects. TemplateLoader is the centralized handler for loading the templates from file.

C#
#region TemplateLoader
    
public class TemplateLoader: System.Web.UI.Page
{        
    private const string CACHE_HEADER = 
                                  "GridHelper.Templates.";

    public new ITemplate LoadTemplate(string templateName)
    {
      ITemplate Template  = null;
      string CachedTemplate = 
                string.Concat(CACHE_HEADER,templateName); 

        
      Template = 
        this.Context.Cache.Get(CachedTemplate) as ITemplate;

      if (Template  == null)
      {
        Template = base.LoadTemplate(templateName); 
              
        this.Context.Cache.Insert(CachedTemplate, Template,
          new System.Web.Caching.CacheDependency(Server.MapPath(templateName)), 
          System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromHours (1),
          System.Web.Caching.CacheItemPriority.AboveNormal, null);
      }
        
      return Template;
    }
}
#endregion

The method is shadowed to enable caching of template objects. If a file is changed, it will be invalidated from the cache and reloaded during the next request.

Gridformats

GridFormats is nothing more than a container class for our grid columns. The main entry into the GridFormats class is the gridname property, therefore you must have unique gridnames in your Web.Config file. The Column and Grid objects are serialized into the GridFormats class using XmlSerialization and the custom ConfigurationSectionHandler object.

C#
#region Column    
[XmlRoot("Column")]
public class Column
{        
    private string _Type = string.Empty;
    private string _Path  = string.Empty;
    private string _HeaderText = string.Empty;
    private string _DataField = string.Empty;
    private bool _Wrap = false;

    [XmlAttributeAttribute("datafield")]
    public string DataField 
    {
        get 
        {
            return _DataField;
        }
        set 
        {
            _DataField = value;
        }
    }
    [XmlAttributeAttribute("type")]
    public string Type 
    {
        get 
        {
            return _Type;
        }
        set 
        {
            _Type = value;
        }
    }

    [XmlAttributeAttribute("path")]
    public string Path
    {
        get
        {
            return this._Path;
        }
        set
        {
            this._Path = value;
        }
    }

    [XmlAttributeAttribute("headertext")]
    public string HeaderText
    {
        get
        {
            return _HeaderText;
        }
        set
        {
            this._HeaderText = value;
        }
    }

    [XmlAttributeAttribute("wrap")]
    public bool Wrap
    {
        get
        {
            return this._Wrap;
        }
        set
        {
            this._Wrap = value;
        }
    }
}
#endregion

#region Grid
[Serializable()]
public class Grid
{
    private Column[] _Columns;
    private string _Name = string.Empty;

    [XmlAttributeAttribute("name")]
    public string Name 
    {
        get 
        {
            return _Name;
        }
        set 
        {
            _Name = value;
        }
    }

    [XmlArray("Columns"), XmlArrayItem("Column")]
    public Column[] Columns 
    {
        get 
        {
            return _Columns;
        }
        set 
        {
            _Columns= value;
        }
    }
}
#endregion

#region GridFormats

[SerializableAttribute()]
public class GridFormats : 
  System.Collections.Specialized.NameObjectCollectionBase, 
                                           IXmlSerializable
{
    public Grid this[int index]
    {
        get 
        {
            return base.BaseGet(index) as Grid; 
        }
        set
        {
            this.BaseSet(index, value);
        }
    
    }

    public Grid this[string index]
    {
        get 
        {
            return (base.BaseGet(index) as Grid);
        }
        set 
        {
            this.BaseSet(index, value); 
        }
    }

    public void Add(Grid Item)
    {
        base.BaseAdd(Item.Name, Item);
    }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer l_Serializer = null;
        l_Serializer = new XmlSerializer(typeof(Grid));
        reader.ReadStartElement();
        while ((reader.NodeType != System.Xml.XmlNodeType.EndElement && 
                         reader.NodeType != System.Xml.XmlNodeType.None)) 
        {
            Grid l_Grid;
            l_Grid = ((Grid)(l_Serializer.Deserialize(reader)));
            Add(l_Grid);
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer l_Serializer = null;
        l_Serializer = new XmlSerializer(typeof(Grid));
        foreach (string l_strKey in base.Keys) 
        {
            Grid l_objServer = this[l_strKey];
            l_Serializer.Serialize(writer, l_objServer);
        }
    }
}
#endregion

Here is the Web.Config entries that contain the raw XML for the GridFormats object hierarchy.

XML
<GridFormats 
  assembly="DynamicDataGrids,Version=2.2.0.0,
              Culture=neutral,PublicKeyToken=null" 
  type="DynamicDataGrids.GridFormats">
    <Grid name="Employees">
        <Columns>
            <Column type="template" 
                path="~/GridTemplates/LastNameFirstNameTemplate.ascx" 
                headertext="Name" wrap="false"></Column>
            <Column type="bound" headertext="Title" 
                datafield="Title" wrap="false"></Column>
            <Column type="template" 
                path="~/GridTemplates/DateTemplate.ascx" 
                headertext="B-Day" wrap="false"></Column>
        </Columns>
    </Grid>    
    <Grid name="Products">
        <Columns>                            
        </Columns>
    </Grid>        
    <Grid name="Customers">
        <Columns>            
        </Columns>
    </Grid>        
</GridFormats>

Each column is defined inside the Columns tag and contains the following properties:

  • type: Determines the type of the column to create. In this example the types do not define serialized objects, i.e. there is no template column object inside the code. You could add that to the code if you wish and create inherited Column objects that define unique properties for each type of column.
  • headertext: The header text for the column.
  • path: Used for template columns only, this defines the path to the template file. You will notice a tilda '~' mark in the examples. Failure to add this to the template path will cause an invalid path exception in the project.
  • datafield: Used by bound columns, this field determines which field to bind to in the incoming data.
  • wrap: Used to turn on/off wrapping in the column.

You could add every possible tag and matching attribute in the XML/class where you need to create unique columns. (CSS, etc.)

Template example

Below is an example of a template that takes two columns from the incoming data source and joins them together to create a Lastname, Firstname link. Note that there is no code-behind for this class. The OnDataBinding event calls the BindData method which hides the link if the last name is empty:

C#
<%@ Control Language="C#" %>

<asp:LinkButton id=lnkLastFirstName runat="server" 
     Text=<%# string.Format("{0}, {1}", 
         DataBinder.Eval(Container, "DataItem.LastName" ), 
         DataBinder.Eval(Container, "DataItem.FirstName" ))%>
     OnDataBinding=BindData ></asp:LinkButton>

<script runat="server">
    void BindData(Object sender, EventArgs e)
    {        
        System.Web.UI.WebControls.DataGridItem container = 
          (System.Web.UI.WebControls.DataGridItem) this.NamingContainer;
                    
        string LastName = 
            DataBinder.GetPropertyValue(container.DataItem, 
                                    "LastName", string.Empty);        
        
        if (LastName == string.Empty)
        {
            LinkButton Link = (LinkButton) sender;
            
            Link.Visible = false;
        
        }
      

    }
</script>

GridHelper

The Gridhelper utilizes the TemplateLoader and GridFormats object to generate columns and append them into the grid. This class is very simple and can be modified/expanded to fit your needs.

Performance

This is always a major concern if you are in a high-volume environment. Caching the templates increases the performance quite a bit, but the main penalty involved in using this approach comes from DataBinder.Eval. The good news is this can be avoided! For all the samples that I have provided, you can change the code to directly cast to your object type (DataView, custom object, etc.) and avoid the DataBinder reflection penalty. I did not perform a detailed performance analysis on the GridHelper class, but I believe the performance from this class will be similar to adding columns to a grid at runtime using custom code. If anyone sees any major performance issues, please let me know and I will resolve them immediately.

History

In my full source code, I do have the ability to call ITemplate columns defined in code and add them into the grid using reflection. Once I clean that code up, I will post it here along with a full implementation of the template columns. Of course, that will only be done if others would like to have it otherwise I am onto my new project. :)

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSize property has an invalid size of 0 Pin
bestwerx20-Jun-06 5:05
bestwerx20-Jun-06 5:05 
Questionadding rows and column in datagridview Pin
thepityone16-Feb-06 20:37
thepityone16-Feb-06 20:37 
QuestionQuestion Pin
Anonymous14-Oct-05 6:42
Anonymous14-Oct-05 6:42 
Generalinterested... Pin
SakiMan2911-Oct-05 6:53
SakiMan2911-Oct-05 6:53 
i see many people using templates for master pages now.

i never considered this for datagrids.

will this work with datarepeater?

sorry for my english

thx

sm
QuestionHow to dynamically add horizontal scrollbars to your browser... Pin
Robert Rohde10-Oct-05 9:19
Robert Rohde10-Oct-05 9:19 
AnswerRe: How to dynamically add horizontal scrollbars to your browser... Pin
Michael.Piccolo10-Oct-05 9:35
Michael.Piccolo10-Oct-05 9:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.