Use Case:
We will be creating a custom Attribute class that will take "IList" based collections and convert them into a "DataTable".
Further we will take the converted DataTable and bind it to a DataGridView with the help of a sample application to elaborate our Custom Attribute class we made.
Using your Visual Studio create a "Class Library" based project:
Next in the project create a new class with the name: TableColumns next add the following code to your class:
/// <summary>
/// Repsent a custom attribute which specifes the meta information about properties
/// of a given type, whether to be shown in DataGridView or not.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class TableColumns : Attribute
{
}
*Note: Whenever we make a CustomAttribute for our use, it has to derive from the Attribute class available from the framework.
In the above code snippet we have specified some rules for our custom attribute class that resticts the usage of our custom attribute: Let us see in detail what each will do:
AttributeTargets.Property:
This tells the compiler that our custom attribute will be applicable only to the properties (i.e. getters and setters can only be tagged with our attribute).
AllowMultiple = false:
This tells the compiler that not more than once can we apply a custom attribute to the same element.
Inherited = true:
This line tells the compiler whether the classes deriving from the Base class using this Atrribute should inherit these attribute tags in the derived class or not (we will see this in more detail using a code snippet).
We will see further in our sample application how these fileds effect the working of our custom attribute.
Now let us move ahead and complete our custom attribute class. Declare two private variables for the class:
private MappingType m_columnMapping;
private string m_columnName = null;
And make an overloaded constructor along with a default constructor:
/// <summary>
/// Default Constructor.
/// </summary>
public TableColumns() { }
/// <summary>
/// Overloaded Constructor.
/// </summary>
/// <param name="columnName">The name of the column.</param>
/// <param name="mappingType">The type of mapping.</param>
public TableColumns(string columnName, MappingType mappingType)
{
m_columnMapping = mappingType;
m_columnName = columnName;
}
We are initializing the private variables declared in the beginning here in the overloaded constructors.
/// <summary>
/// Gets or Sets the name of the column.
/// </summary>
public string ColumnName
{
get { return m_columnName; }
set { m_columnName = value; }
}
/// <summary>
/// Gets or Sets the mapping type associated with the column.
/// </summary>
public MappingType ColumnMappingType
{
get { return m_columnMapping; }
set { m_columnMapping = value; }
}
Ok, with the above code we are finished creating our Custom Attribute Class that will be used on the Model Classes to tag with.
Now next we need an extension method to read the data from objects which are tagged with our Custom Attribute classes. For that add a new class in the project named:
ListExtensions.cs
public static class ListExtensions
{
}
Next tag the class with a static attribute (as the extension methods can live in static classes only); then add the following method:
/// <summary>
/// Extension Method to convert the IList type source to a DataTable
/// </summary>
/// <param name="_source">Any type that implements IList interfaces.</param>
/// <returns></returns>
public static DataTable ToDataTable(this IList _source)
{
}
We have made an extension method ToDataTable that will be available for ICollection implemented classes and can create a DataTable out of it.
Next we need to determine the properties that are tagged with our CustomAttribute "TableColumns", and all these properties need to be converted to corresponding DataColumns of the DataTable; for that we implement the following:
if (_source != null && _source.Count > 0)
{
DataTable dataTable = new DataTable();
Type elementType = _source[0].GetType();
dataTable.TableName = elementType.Name;
PropertyInfo[] propertyInfo = _source[0].GetType().GetProperties();
for (int i = 0; i < propertyInfo.Length; i++)
{
Attribute[] attInfo = propertyInfo[i].GetCustomAttributes(typeof(TableColumns), false) as Attribute[];
if (attInfo != null && attInfo.Length > 0)
{
for (int j = 0; j < attInfo.Length; j++)
{
TableColumns attribute = attInfo[j] as TableColumns;
DataColumn column = new DataColumn();
if (attribute.ColumnName == string.Empty || attribute.ColumnName == null)
{
column.ColumnName = propertyInfo[i].Name;
}
else
{
column.ColumnName = attribute.ColumnName;
}
if (attribute.ColumnMappingType == MappingType.Hidden)
column.ColumnMapping = attribute.ColumnMappingType;
column.DataType = propertyInfo[i].PropertyType;
dataTable.Columns.Add(column);
}
}
}
Ok let us dissect what we are doing here:
Type elementType = _source[0].GetType();
dataTable.TableName = elementType.Name;
Here we are getting the type of the elements that our IList stores and based on that we give a default name to our DataTable, next using reflection we are getting the properties available for that type:
PropertyInfo[] propertyInfo = _source[0].GetType().GetProperties();
In the subsequent code we loop through all the properties and find all the properties tagged with our custom attribute:
Attribute[] attInfo = propertyInfo[i].GetCustomAttributes(typeof(TableColumns), false) as Attribute[];
From the above piece of code we get the attribute information and based on that we create as many DataColumns. Next follows:
if (attribute.ColumnName == string.Empty || attribute.ColumnName == null)
{
column.ColumnName = propertyInfo[i].Name;
}
else
{
column.ColumnName = attribute.ColumnName;
}
In case there is no explicit ColumnName specified in the Attribute tagged over the property we name the DataColumn with the name of the Property.
if (attribute.ColumnMappingType == MappingType.Hidden)
column.ColumnMapping = attribute.ColumnMappingType;
Now similarly if MappingType specified is Hidden, the same is specified in the DataColumn to hide that column in the while displaying on screen. And by default the data type of the DataColumn is that of Property.
column.DataType = propertyInfo[i].PropertyType;
This concludes our DataTable schema creation; next we need to extract data from the list and create rows in the DataTable which will be eventually returned, for that:
for (int i = 0; i < _source.Count; i++)
{
int j = 0;
object[] fieldValues = new object[dataTable.Columns.Count];
for (; j < propertyInfo.Length; j++)
{
MemberInfo mi = elementType.GetMember(propertyInfo[j].Name)[0];
if (mi.MemberType == MemberTypes.Property)
{
PropertyInfo pi = mi as PropertyInfo;
fieldValues[j] = pi.GetValue(_source[i], null);
}
}
dataTable.Rows.Add(fieldValues);
}
We again make use of reflection to get the Members of the object and figure out whether they are properties and eventually make use of GetValue() function to get their values and fill up the rows.
This concludes our first part where we made a Custom Attribute and implemented an Extension method that will enable us to use that Custom Attribute. In the next series we will see how we can leverage the Custom Attribute we made in an application.