Introduction
The following step by step example will demonstrate how you can use Aspect Oriented Programming (AOP) to cast a collection of objects into a DataTable. This particular example will leverage the power of attributes, generics, and reflection to explicitly convert a collection of container classes into a DataTable.
I should state for the record that this article was inspired in part by a cogent book on the topic: Applied .NET Attributes by Jason Bock and Tom Barnaby.
Step 1: Build your own Custom Attribute Class
Custom Attributes always inherit from System.Attribute, in fact any class that inherits from System.Attribute whether directly or indirectly is an attribute class. Attribute classes also follow the convention of having the word �Attribute� attached as a suffix to the class name.
Attributes allow you to add metadata to an object that you can read at run-time via reflection. As a result, they provide an elegant (and granular) solution to the object oriented problem of cross-cutting concerns.
Our first step will be to build a custom attribute class that will allow us to acquire meta data about the properties of our container class (which we haven�t built yet) at run-time. The beauty of this solution is that we can use our custom attribute class (in this case ConversionAttribute) to decorate any class that we decide to add to our project at a later date.
using System;
namespace Coreweb.Example
{
[AttributeUsage(AttributeTargets.Property)]
public class ConversionAttribute : Attribute
{
private bool m_dataTableConversion;
private bool m_allowDbNull;
private bool m_keyField;
public ConversionAttribute() { }
public bool DataTableConversion
{
get { return m_dataTableConversion; }
set { m_dataTableConversion = value; }
}
public bool AllowDbNull
{
get { return m_allowDbNull; }
set { m_allowDbNull = value; }
}
public bool KeyField
{
get { return m_keyField; }
set { m_keyField = value; }
}
}
}
Step 2: Build a Container Class
Now we create a container class! Notice how I've decorated the properties of this class with the attributes we�ve created in Step 1. In Step 4 we'll use this information to build a DataTable
via reflection.
using System;using System.Collections; namespace Coreweb.Example
{
public class Employee
{
private string m_firstName;
private string m_lastName;
private DateTime m_birthDate;
private ArrayList m_aList;
public Employee(string firstName, string lastName, DateTime birthDate)
{
m_firstName = firstName;
m_lastName = lastName;
m_birthDate = birthDate;
}
[Conversion(DataTableConversion = true, KeyField = true,
AllowDbNull = false)]
public string FirstName
{
get { return m_firstName; }
set { m_firstName = value; }
}
[Conversion(DataTableConversion = true, KeyField = true,
AllowDbNull = false)]
public string LastName
{
get { return m_lastName; }
set { m_lastName = value; }
}
[Conversion(DataTableConversion = true, AllowDbNull = false)]
public int Age
{
get { return this.GetAge(); }
}
[Conversion(DataTableConversion = true, AllowDbNull = true)]
public DateTime BirthDate
{
get { return m_birthDate; }
set { m_birthDate = value; }
}
public ArrayList AList
{
get { return m_aList; }
set { m_aList = value; }
}
private int GetAge()
{
int years = DateTime.Now.Year - m_birthDate.Year;
if (DateTime.Now.Month < m_birthDate.Month ||
(DateTime.Now.Month == m_birthDate.Month &&
DateTime.Now.Day < m_birthDate.Day))
{
years--;
}
return years;
}
}
}
Step 3: Create an Interface - IDataTableConverter
For purpose of this example we�ll be converting a generic list (System.Collections.Generic.List) to a DataTable. The implementation for a dictionary might be different so we�ll want to leverage the power of an interface to abstract away from any specific implementation.
using System;
using System.Collections.Generic;
using System.Data;
namespace Coreweb.Example
{
public interface IDataTableConverter<T>
{
DataTable GetDataTable(List<T> items);
}
}
Step 4: Build a DataTableConverter Class
This is the class that will be doing all the work. Essentially, this class uses the System.Reflection namespace to query attributes at run-time, build a DataTable schema, and fill it.
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
namespace Coreweb.Example
{
public class DataTableConverter<T> : IDataTableConverter<T>
{
private bool m_enforceKeys;
public DataTableConverter() { }
public DataTableConverter(bool enforceKeys)
{
m_enforceKeys = enforceKeys;
}
public DataTable GetDataTable(List<T> items)
{
DataTable dt;
try
{
dt = this.ConstructDataTableSchema(items[0]);
}
catch (IndexOutOfRangeException ex)
{
throw (new ApplicationException(
"Cannot convert List of zero length to a " +
"DataTable", ex));
}
if (dt != null)
{
{
DataRow dr = dt.NewRow();
Type type = items[i].GetType();
MemberInfo[] members = type.GetProperties();
foreach (MemberInfo member in members) {
object[] attributes = member.GetCustomAttributes(true);
if (attributes.Length != 0)
{
foreach (object attribute in attributes)
{
ConversionAttribute ca = attribute as
ConversionAttribute;
if (ca != null)
{
if (ca.DataTableConversion)
{
string[] nameArray
= member.Name.ToString().Split(
Convert.ToChar(" "));
PropertyInfo prop = type.GetProperty(
nameArray[0]);
Type valueType = prop.GetValue(items[i],
null).GetType();
dr[nameArray[0]] = prop.GetValue(items[i],null);
}
}
}
}
}
dt.Rows.Add(dr);
}
return dt;
}
else
{
throw new ApplicationException("List items are not convertable.");
}
}
private DataTable ConstructDataTableSchema(T item)
{
string tableName = string.Empty;
List<DTCONVERTERCONTAINER> schemaContainers = new List<DTCONVERTERCONTAINER>();
Type type = item.GetType();
MemberInfo[] members = type.GetProperties();
foreach (MemberInfo member in members)
{
object[] attributes = member.GetCustomAttributes(true);
if (attributes.Length != 0)
{
foreach (object attribute in attributes)
{
ConversionAttribute ca = attribute as ConversionAttribute;
if (ca != null)
{
if (ca.DataTableConversion)
{
string[] classNameArray =
member.ReflectedType.ToString().Split(
Convert.ToChar("."));
tableName = classNameArray[classNameArray.Length- 1];
string name = member.Name.ToString();
PropertyInfo prop = type.GetProperty(name);
Type valueType = prop.GetValue(item, null).GetType();
schemaContainers.Add(new DTConverterContainer(name,
valueType, ca.AllowDbNull, ca.KeyField));
}
}
}
}
}
if (schemaContainers.Count > 0)
{
DataTable dataTable = new DataTable(tableName);
DataColumn[] dataColumn = new DataColumn[schemaContainers.Count];
int totalNumberofKeys = 0;
foreach (DTConverterContainer container in schemaContainers)
{
if (container.IsKey == true && m_enforceKeys == true)
{
totalNumberofKeys = totalNumberofKeys + 1;
}
}
DataColumn[] keyColumnArray = new DataColumn[totalNumberofKeys];
int keyColumnIndex = 0;
for (int i = 0; i < schemaContainers.Count; i++)
{
dataColumn[i] = new DataColumn();
dataColumn[i].DataType = schemaContainers[i].PropertyType;
dataColumn[i].ColumnName = schemaContainers[i].PropertyName;
dataColumn[i].AllowDBNull = schemaContainers[i].AllowDbNull;
dataTable.Columns.Add(dataColumn[i]);
if (schemaContainers[i].IsKey == true &&
m_enforceKeys == true)
{
keyColumnArray[keyColumnIndex] = dataColumn[i];
keyColumnIndex = keyColumnIndex + 1;
}
}
if (m_enforceKeys)
{
dataTable.PrimaryKey = keyColumnArray;
}
return dataTable;
}
return null;
}
private class DTConverterContainer
{
private string m_propertyName;
private Type m_propertyType;
private bool m_allowDbNull;
private bool m_isKey;
internal DTConverterContainer(string propertyName, Type propertyType,
bool allowDbNull, bool isKey)
{
m_propertyName = propertyName;
m_propertyType = propertyType;
m_allowDbNull = allowDbNull;
m_isKey = isKey;
}
public string PropertyName
{
get { return m_propertyName; }
set { m_propertyName = value; }
}
public Type PropertyType
{
get { return m_propertyType; }
set { m_propertyType = value; }
}
public bool AllowDbNull
{
get { return m_allowDbNull; }
set { m_allowDbNull = value; }
}
public bool IsKey
{
get { return m_isKey; }
set { m_isKey = value; }
}
}
}
}
Step 5: Build your own Generic List
Here were we add the explicit conversion operator that will allow our list to be converted to a DataTable. If you want to learn more about type conversions in C# I recommend you read the following (excellent) article by Rajesh V.S.: Type Conversions.
using System;
using System.Collections.Generic;
using System.Data;
namespace Coreweb.Example
{
public class CoreWebList<T> : List<T>
{
private static bool m_enforceKeysInDataTableConversion;
public CoreWebList()
{
m_enforceKeysInDataTableConversion = false;
}
public bool EnforceKeysInDataTableConversion
{
get { return m_enforceKeysInDataTableConversion; }
set { m_enforceKeysInDataTableConversion = value; }
}
public static explicit operator DataTable(CoreWebList<T> list)
{
IDataTableConverter<T> converter = new DataTableConverter<T>(
m_enforceKeysInDataTableConversion);
return converter.GetDataTable(list);
}
}
}
Step 6: Build your test harness
In this step we're going to fill our collection with Employees, explicitly convert to a DataTable, and then bind to DataGrid.
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="ConversionTestHarness.aspx.cs"
Inherits="ConversionTestHarness" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Conversion Test Harness</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DataGrid ID="dgTest" runat="server"></asp:DataGrid>
</div>
</form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Coreweb.Example;
public partial class ConversionTestHarness : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.DemonstrationTest();
}
private void DemonstrationTest()
{
CoreWebList coreWebTestList = new CoreWebList();
coreWebTestList.Add(new Employee("Albert", "Einstein",
Convert.ToDateTime("3/14/1879")));
coreWebTestList.Add(new Employee("John", "von Neumann",
Convert.ToDateTime("12/28/1903")));
coreWebTestList.Add(new Employee("Joe", "Finsterwald",
Convert.ToDateTime("1/18/1969")));
coreWebTestList.Add(new Employee("Erwin", "Schr�dinger",
Convert.ToDateTime("8/12/1887")));
DataTable dt = (DataTable)coreWebTestList;
DataView dv = new DataView(dt);
dv.Sort = "BirthDate";
dgTest.DataSource = dv;
dgTest.DataBind();
}
}
Conclusion:
Now you have the ability to explicitly convert any decorated container class from a List to a DataTable! Hopefully I�ve also piqued your interest in Aspect Orient Programming!