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.
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 {
public string Name { get; set; }
public double Balance { get; set; }
}
public Account[] Accounts;
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 {
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 = ...
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";
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 struct
s:
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 Array
s and ArrayList
s 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.