Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Binding: DataGrid to Vertical Table

0.00/5 (No votes)
3 Jan 2005 1  
A simple way to bind a Vertical Table with a DataGrid in ASP.NET
In this article, we will illustrate practical aspects of binding the ASP.NET DataGrid component to certain custom data sources, especially a Vertical Table, which stores data in arrays of columns rather than arrays of records. Examples start with traditional ADO.NET DataTable, through arrays of custom records and finishes with the Vertical Table in a comparative fashion.

Sample Image - AspNetBindDatagridVT.gif

Background

Traditionally, database approach represents table as a list of records. Equivalent of this in a programming language would be a one-dimensional array (list) of structures:

public struct Account {                  // record
    public string Name { get; set; }
    public double Balance { get; set; }
}

public Account[] Accounts;               // table

However, in some applications, for performance and other reasons, it is feasible to transpose the data structure into a collection of lists, where an item of the collection is a list of values for a given column. In a programming language, it can be represented as follows:

public class Accounts {                 // vertical table
    public string[] Name;
    public double[] Balance;
}

Such a data structure, consisting of a collection of columns, will be called a Vertical Table.

Binding of a Data Table

One of the most common data sources in ASP.NET is a DataSet member, also known as DataTable. We will use this type of binding as the starting point for comparison with the other data types.

All the examples in the code consist of the same type of .aspx file accompanied with different code-behind files. The body of the .aspx file contains a DataGrid.

<form id="Form1" method="post" runat="server">
    <asp:datagrid id="DataGrid1" runat="server">
    </asp:datagrid>
</form>

The skeleton of the code-behind class looks as follows:

Note: Only parts marked with ellipsis (...) will differ between data sources, namely, the DataType, CreateDataSource and GenerateColumns.

public class DataSetBind : System.Web.UI.Page
{
    protected System.Web.UI.WebControls.DataGrid DataGrid1;

    protected DataType dt = ... // Particular data source type

    private void CreateDataSource() {
        // ...
    }
    private void GenerateColumns(DataGrid grid) {
        // ...
    }
    private void Page_Load(object sender, System.EventArgs e)
    {
        CreateDataSource();
        DataGrid1.DataSource = dt;

        GenerateColumns(DataGrid1);

        DataBind();
    }

    #region Web Form Designer generated code
    // ...
}

For DataTable source type, we create data source by specifying the schema and filling out the rows.

protected DataTable dt = new DataTable("ACCOUNT");

private void CreateDataSource() {
    DataColumn col = null;
    
    col = dt.Columns.Add("NAME", typeof(string));
    col.Caption = "Name";

    col = dt.Columns.Add("BALANCE", typeof(double));
    col.Caption = "Balance";

    // Fill data table
    dt.Rows.Add(new object[]{"One", 123.45});
    dt.Rows.Add(new object[]{"Two", 0});
    dt.Rows.Add(new object[]{"Three", -123.45});
}

In principle, that should be enough to be able to bind to a DataGrid, unless you care about setting captions different from the column data member names (Title case vs. all caps here). AutoGenerateColumns property of DataGrid is on by default, but it ignores the Caption of DataColumn. So, we need to make special effort, to set it manually.

private void GenerateColumns(DataGrid grid) {

    grid.AutoGenerateColumns = false;

    foreach (DataColumn col in dt.Columns) {
        BoundColumn dc = new BoundColumn();
        dc.DataField = col.ColumnName;
        dc.HeaderText = col.Caption;
        grid.Columns.Add(dc);
    }
}

Binding of an Array of Custom Records

In ASP.NET, there is a built-in mechanism to bind to either an Array or ArrayList of custom records. It follows from the remarks in BaseDataList.DataSource property definition.

We will use the custom record of such a form:

public struct TestStruct {
    private string _NAME;
    private double _BALANCE;

    [View("Name")]
    public string NAME { get { return _NAME; } }
    [View("Balance")]
    public double BALANCE { get { return _BALANCE; } }
    
    public TestStruct(string NAME, double BALANCE) {
        _NAME = NAME;
        _BALANCE = BALANCE;
    }
}

The data source will be a simple array of our structs:

protected TestStruct[] dt = null;

private void CreateDataSource() {
    dt = new TestStruct[]{
                new TestStruct("One", 123.45), 
                new TestStruct("Two", 0),
                new TestStruct("Three", -123.45),
    };
}

Using Attributes to Specify Captions Declaratively

We will get creative here by encapsulating captions in the declaration of the struct and discovering them generically in the column generation routine. First, we define a custom attribute class as shown below:

public class ViewAttribute : Attribute {
    protected string _caption = "";
    protected string _format = "";
    
    public ViewAttribute(string caption) : this(caption, "") { }
    public ViewAttribute(string caption, string format) {
        _caption = caption;
        _format = format;
    }
    public string Caption { get { return _caption; } }
    public string Format { get { return _format; } }
}

The attribute was used as [View("Name")] in the struct declaration above.

Then the column generation will look as follows.

Note: Such a discovery will allow selective display of struct properties that are marked with the View attribute, hiding all others, thus allowing more properties for other uses.

private static void GenerateColumns(DataGrid grid) {

    grid.AutoGenerateColumns = false;

    foreach (PropertyInfo pi in typeof(TestStruct).GetProperties()) {
        ViewAttribute[] attrs = 
            pi.GetCustomAttributes(typeof(ViewAttribute), false) 
                                        as ViewAttribute[];
        if (attrs.Length <= 0)
            continue;
        BoundColumn dc = new BoundColumn();
        dc.DataField = pi.Name;
        dc.HeaderText = attrs[0].Caption;
        dc.DataFormatString = attrs[0].Format;
        grid.Columns.Add(dc);
    }
}

Binding of Vertical Table

When it comes to a Vertical Table, there is no such automatic support in ASP.NET DataGrid as for a DataTable of Arrays and ArrayLists of custom records.

Our strategy will be based on the definition of the DataSource property, which states that the data source must be an object that implements the System.Collections.IEnumerable interface. The enumerator will traverse a list of records similar to the Custom Record example, however the record will not be an actual container of the data, but rather an accessor, exposing with its properties the data actually lying inside the columns of the Vertical Table. Such a design model is called Flyweight Pattern.

The Vertical Table class, whose members are the column arrays of values, will serve as the IEnumerable.

public class TestTable : IEnumerable {
    internal string[] _NAME = null;
    internal double[] _BALANCE = null;

    public TestTable(string[] NAME, double[] BALANCE) {
        _NAME = NAME;
        _BALANCE = BALANCE;
    }

    public int Count { get { 
                  return _NAME == null ? 0 : _NAME.Length; }}

    #region IEnumerable Members

    public IEnumerator GetEnumerator() {
        return new TestRecord(this);
    }

    #endregion
}

IEnumerable.GetEnumerator() will return a fresh instance of our custom record accessor, which will also serve as IEnumerator over itself. It will simply iterate an index into the array position in the referred Vertical Table.

public class TestRecord : IEnumerator {
    TestTable _testTable = null;
    int _pos = -1;

    [View("Name", "")]
    public string NAME { get { return _testTable._NAME[_pos]; } }
    [View("Balance", "<tt>{0:F2}</tt>")]
    public double BALANCE { get { return _testTable._BALANCE[_pos]; } }

    public TestRecord(TestTable testTable) {
        _testTable = testTable;
    }

    #region IEnumerator Members

    public bool MoveNext() {
        if (_pos >= _testTable.Count - 1)
            return false; 
        _pos++; 
        return true;
    }
    public object Current { get { return this; } }
    public void Reset() { _pos = -1; }

    #endregion
}

Note how we use the same View attribute for column generation. Only properties thus marked will make it into columns of the DataGrid. So, we use (almost) the same GenerateColumns routine, discovering the custom attributes of the accessor record.

Also, note the trick used here to set DataFormatString, by means of the format part of the View attribute, partly with well-formed HTML (<tt>), which is used duely by the DataGrid renderer for formatting effects.

Creating the data source looks as follows:

protected TestTable dt = null;

private void CreateDataSource() {
    dt = new TestTable(
        new string[]{"One", "Two", "Three"},
        new double[]{123.45, 0, -123.45});
}

Points of Interest

Design decisions used here serve illustrative purposes of DataGrid binding. In real-life situations, further enhancements can be made, such as generalizing custom records and Vertical Table to handle number of columns and records dynamically.

Despite the promise of symmetry between Windows.Forms and Web.UI, DataGrid binding works very differently, when stepping out of the standard case of DataSet. This is due to the two-way interactive nature of Windows.Forms binding as compared to Web.UI. Similar use of Windows.Forms binding will be the subject of a separate study.

References

History

  • 2nd January, 2005: Oleg Kobchenko - Initial writing

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here