Now I am going to develop the data binding logic of the TitleDescription control. It will be explained after the listing:
private string pDataTitleField;
private string pDataDescriptionField;
// indicates which column the title value resides in
public string DataTitleField {
get { return pDataTitleField; }
set {
if ((value == null) && (value.Length == 0))
throw new ArgumentNullException();
else
pDataTitleField = value;
}
}
// indicates which column the description value resides in
public string DataDescriptionField {
get { return pDataDescriptionField; }
set {
if ((value == null) && (value.Length == 0))
throw new ArgumentNullException();
else
pDataDescriptionField = value;
}
}
public object DataSource {
get { return ViewState["DataSource"]; }
set {
if (value is DataTable) {
// finally accepts a data table – through conversion or straight
// from the page developer. However, we take precaution to that it is trimmed
DataTable externalTable = (DataTable)value;
DataTable internalTable = new DataTable();
Hashtable columns = FindDataColumns();
foreach (string key in columns.Keys)
internalTable.Columns.Add(new DataColumn(key));
foreach (DataRow eRow in externalTable.Rows) {
DataRow iRow = internalTable.NewRow();
internalTable.Rows.Add(iRow);
foreach (DataColumn iColumn in internalTable.Columns)
iRow[iColumn] = eRow[(string)columns[iColumn.ColumnName]];
}
ViewState["DataSource"] = internalTable;
}
else if (value is DataSet) {
// deals with data sets and selects the first data table
if (((DataSet)value).Tables.Count == 0)
throw new Exception("The dataset does not have any tables.");
else
DataSource = ((DataSet)value).Tables[0];
}
else {
// we are now dealing with more advanced objects.
// Additional things need to be setup to ensure best performance.
DataTable table = new DataTable();
Hashtable columns = FindDataColumns();
table.Columns.Add(new DataColumn("Title"));
table.Columns.Add(new DataColumn("Description"));
if (value is IDataReader) {
// this deals with all the data readers
int nextColumn = 0;
IDataReader reader = (IDataReader)value;
while (reader.Read()) {
DataRow row = table.NewRow();
table.Rows.Add(row);
foreach (string key in columns.Keys) {
if (columns[key] == null) {
row[key] = reader[nextColumn];
nextColumn++;
}
else
row[key] = reader[columns[key].ToString()];
}
}
}
else if (value is IEnumerable) {
// this deals with the data views, custom collections and XML nodes.
// The problem is that this may be more time consuming
foreach (object obj in (IEnumerable)value) {
DataRow row = table.NewRow();
table.Rows.Add(row);
foreach (string key in columns.Keys)
row[key] = DataBinder.Eval(obj, columns[key].ToString());
}
}
else
// we cannot accept the data source – we do not recognize it
throw new Exception("Invalid Data Source type.");
DataSource = table;
}
}
}
private Hashtable FindDataColumns() {
Hashtable columns = new Hashtable();
// use the default column if none selected
columns.Add("Title", DataTitleField);
columns.Add("Description", DataDescriptionField);
return columns;
}
Right – there are plentiful of comments in the above figure. I do not think that anyone will have a problem with it… but I have the feeling that I should add a simple summary. Essentially, what happens is that the data source is recycled on the fly until it reaches a DataTable state. It sort of bubbles a conversion – the above can even bind to XML – XMLNodes to be exact for now. But the DataTable class does not have to be the end objective of every data binding scenario: it can be XML itself – it all depends on you as the developer.
With the above code sample, it is picky on the columns that it does save – the reason is to down size view state. We could be given a data reader with ten columns and we only need two of them… why save the rest when we are not going to use it? The data in the view state can get tremendously big because we are saving data that we do not need in the first place.
However, if you do have a template based control – there is no telling what data is required. To combat this, you will unfortunately have to save the entire DataTable to the view state – and that is the very reason why controls like the DataGrid control and the Repeater control can have a huge footprint on the view state.
There is one thing missing from the TitleDescription control – and that is the DataBind method. If you have a quick breeze through the documentation, you would have noticed that there is a DataBind method on the Control class. The DataBind method is marked virtual so we can easily override it:
public override void DataBind() {
if (DataSource != null) {
foreach (DataRow row in ((DataTable)DataSource).Rows) {
TitleDescriptionItem item = new TitleDescriptionItem();
Items.Add(item);
item.DataItem = row;
item.DataBind();
}
}
else
throw new Exception("DataSource is null.");
}
That code seems simple enough – assign a sub-data source to the new TitleDescription control and bind it. Simple as that and the binding logic of that control falls onto the next page.