Introduction
At the end of this article, you should be comfortable using Custom Attributes.
This article focuses on using them in order to create a quick, flexible data binding mechanism.
Prerequisite Experience/Knowledge
At least a beginner's understanding of ADO.NET and Reflection is required to follow this article.
Understanding Attributes
Attributes are used to add additional metadata to any element within a class (including the class itself).
If you've ever customized a class' XML serialization, you've done it using attributes. The syntax for any attribute is:
[<Attribute>(<attribute values>)] <target element>
The syntax for defining an attribute is to place the following System attribute before the class that will define your custom:
[AttributeUsage(AttributeTargets.<ElementThisCanApplyTo>,
AllowMultiple = (true | false)]
The Attribute itself can target a class, constructor, field, method, property or all (meaning the attribute could be applied to anything, but you need to be careful with this depending on what you'll be using the attribute to indicate). The class definition for an attribute is quite simple. The constructor(s) for the class are what determines the valid signatures which can be used when applying the attribute to an element. Hang in there, this will all become clear when we take a look at how to implement your first custom attribute. The attribute class is essentially a custom data structure which you will use to attach some metadata to a class element for later retrieval. Let's look at two custom attributes which we'll be using for our data binding.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ClassDataTable : Attribute
{
private string m_strDataTableName = "";
public string TableName
{
get{return m_strDataTableName;}
set{m_strDataTableName=value;}
}
public ClassDataTable(string strDataTableName)
{
m_strDataTableName = strDataTableName;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class FieldDataColumn : Attribute
{ private string m_strFieldColumnName = "";
public string ColumnName
{
get{return m_strFieldColumnName;}
set{m_strFieldColumnName = value;}
}
public FieldDataColumn(string strFieldColumnName)
{
m_strFieldColumnName = strFieldColumnName;
}
Now that we have our custom attributes, let's apply them to a new class object.
[ClassDataTable("People")]
public class Person
{
private string m_strFirstName = "";
private string m_strLastName = "";
private int m_intAge = 0;
[FieldDataColumn("FirstName")]
public string FirstName
{
get{return m_strFirstName;}
set{m_strFirstName = value;}
}
[FieldDataColumn("LastName")]
public string LastName
{
get{return m_strLastName;}
set{m_strLastName = value;}
}
[FieldDataColumn("Age")]
public int Age
{
get{return m_intAge;}
set{m_intAge = value;}
}
}
Reading Custom Attributes
Perhaps you're not excited yet, after all, all we did was attach string
s in the metadata to a class and you're not even sure how you're supposed to make any use of it (or to access it). Don't worry, it's going to get interesting.
Let's make a class with some static
functionality that will retrieve this metadata we've just attached. I will say in advance that the following class can and should be abstracted into something more generic (and there will be an article on it eventually), but for now, we'll keep things simple and just focus on the task at hand.
If you're not comfortable or even familiar with Reflection, the good news is you're going to be introduced. After you've finished this article though, I suggest you do some serious reading about one of the most powerful features in the .NET Framework.
We need some way to get the metadata we're attaching to classes via our new custom attributes so we'll create a new class called AttributeReader
and give it the following methods: GetBoundTable
and GetBoundColumn
.
public class AttributeReader
{
public static string GetBoundTable(object objTarget)
{
Type objType = objTarget.GetType();
ClassDataTable objBoundTable = (ClassDataTable)
objType.GetClassAttributeByType (objTarget, typeof(ClassDataTable))[0];
return objBoundTable.TableName;
}
public static string GetBoundColumn(object objTarget,
string strFieldName)
{
Type objType = objTarget.GetType();
PropertyInfo objField = objType.GetProperty(strFieldName);
FieldDataColumn objBoundColumn = (FieldDataColumn)
objField.GetCustomAttributes (typeof(FieldDataColumn), true)[0];
return objBoundColumn.ColumnName;
}
}
All Together Now...
Now that we've got the custom attributes defined, a class object with them applied and a way to read them after the fact, here's a short listing which demonstrates how all this gets used. To test all this together, you will need to include the class listings above along with this to make a little console application which should give you a foundation to work with going forward.
I hope this has been a helpful article and will help you in creating better data tiers in the future.
Please keep in mind that the code in this article is intended to show you the principles of data binding using custom attributes and that all of the code should be optimized so that it will be practical and apply to more situations.
public class CustomAttributeTesting
{
public static void Main()
{
DataSet objSet = CreateDataSet();
Person objPerson = GetObjectFromBinding(objSet);
Console.WriteLine("First Name: " + objPerson.FirstName +
"\nLast Name: " + objPerson.LastName +
"\nAge: " + objPerson.Age.ToString());
Console.WriteLine("Press Any Key To Continue");
Console.ReadKey();
}
public static DataSet CreateDataSet()
{
DataSet objSet = new DataSet();
DataTable objTable = new DataTable("People");
objTable.Columns.Add("FirstName", typeof(string));
objTable.Columns.Add("LastName", typeof(string));
objTable.Columns.Add("Age", typeof(int));
objTable.Rows.Add(new object[] {"Alex", "Robson", 27});
objSet.Tables.Add(objTable);
return objSet;
}
public static Person GetObjectFromBinding(DataSet objSet)
{
Person objPerson = new Person();
string strTableName = AttributeReader.GetBoundTable(objPerson);
DataTable objTable = objSet.Tables[strTableName];
DataRow objRow = objTable.Rows[0];
string strColumnName = "";
foreach (PropertyInfo objField in
typeof(Person).GetProperties())
{
strColumnName = AttributeReader.GetBoundColumn (objPerson, objField.Name);
objField.SetValue(objPerson, Convert.ChangeType (objRow[strColumnName],
objField.PropertyType), null);
}
return objPerson;
}
}
History
- 26th September, 2006: Initial post