Today I had a situation where I was calling a web service that returned an array of items. Here is a sample of the XML the web service was returning.
<?
xml version="
1.0"
encoding="
utf-8"
?>
<ItemCollection>
<item>
<FirstName>Keith
</FirstName>
<LastName>Elder
</LastName>
<FavoriteColor>Blue
</FavoriteColor>
</item>
<item>
<FirstName>Chris
</FirstName>
<LastName>Risner
</LastName>
<FavoriteColor>Pink
</FavoriteColor>
</item>
<item>
<FirstName>David
</FirstName>
<LastName>Little
</LastName>
<FavoriteColor>Carnation Pink
</FavoriteColor>
</item>
</ItemCollection>
The web service returned the data as expected but I needed to bind this to a DataGridView in the user interface. For anyone that has used VS2005 you know this is pretty simple. I added a new data source of type "Object" and selected my ItemCollection object that was generated from the WSDL of the web service. I dropped the ItemCollection onto the designer and I had a DataGridView with all of my columns. So far, I've spent about 30 seconds on this and I've connected to a web service and bound data to the UI. I was going to be done with this in a few minutes. I proceeded to take out the columns that I didn't want showing and built the solution and everything looked great.
I then referred back to the spec and noticed something that I missed. The columns need to be sortable in the UI. This poses a problem because the data that is coming back from the web service gets converted into an array of items (items[]). Although we can bind an array to a DataGridView there isn't an easy way to make the DataGridView sort this information based on each column.
I stared at my screen for a few minutes and came to the conclusion that I was going to need to convert the items[] to a DataSet since when bound to a DataGridView it would provide automatic sorting as well as filtering down the road in case someone decided they wanted to search this data (which they typically do). I started then writing a CollectionToDataSet object that would take a collection of items and convert the collection to a DataSet. When you decide to go down this path to make this type of library you really hate building something that only works for one type of object. I first got the new class working but it only worked with the type of object I was working with. During my playing around with the new class a light bulb went off and it hit me this would be a good place to implement a Generic.
Here is the end result of the class which uses a Generic to figure out which type of object is being passed in. The only thing it must do is implement the ICollection interface and things work. Here is what I came up with.
/// <summary>
/// This will take anything that implements the ICollection interface and convert
/// it to a DataSet.
/// </summary>
/// <example>
/// CollectiontoDataSet converter = new CollectionToDataSet<Letters[]>(letters);
/// DataSet ds = converter.CreateDataSet();
/// </example>
/// <typeparam name="T"></typeparam>
public class CollectionToDataSet<T> where T : System.Collections.ICollection
{
T _collection;
public CollectionToDataSet(T list)
{
_collection = list;
}
private PropertyInfo[] _propertyCollection = null;
private PropertyInfo[] PropertyCollection
{
get
{
if (_propertyCollection == null)
{
_propertyCollection = GetPropertyCollection();
}
return _propertyCollection;
}
}
private PropertyInfo[] GetPropertyCollection()
{
if (_collection.Count > 0)
{
IEnumerator enumerator = _collection.GetEnumerator();
enumerator.MoveNext();
return enumerator.Current.GetType().GetProperties();
}
return null;
}
public DataSet CreateDataSet()
{
DataSet ds = new DataSet("GridDataSet");
ds.Tables.Add(FillDataTable());
return ds;
}
private DataTable FillDataTable()
{
IEnumerator enumerator = _collection.GetEnumerator();
DataTable dt = CreateDataTable();
while (enumerator.MoveNext())
{
dt.Rows.Add(FillDataRow(dt.NewRow(),enumerator.Current));
}
return dt;
}
private DataRow FillDataRow(DataRow dataRow, object p)
{
foreach (PropertyInfo property in PropertyCollection)
{
dataRow[property.Name.ToString()] = property.GetValue(p, null);
}
return dataRow;
}
private DataTable CreateDataTable()
{
DataTable dt = new DataTable("GridDataTable");
foreach (PropertyInfo property in PropertyCollection)
{
dt.Columns.Add(property.Name.ToString());
}
return dt;
}
}
There you go! A class to take any type of object that implements the ICollection interface and convert it to a DataSet.
As I was writing this article I thought of another way this could be done. Since the items[] is serializable to XML and a DataSet can take XML and convert that to a DataSet through the ReadXml() method, what I could have done is convert the object to XML, then have a DataSet just read in the XML. Just food for thought. I may play with that later on to see which one is "faster". I can tell you that serializing the object and then converting it would be a lot less code to write initially, however, this class will probably come in handy more than not.
Let me know what you think about the class or if you have any ideas on how to improve it or ways you would have done it differently. I've attached the class file to this post for those that want to download it and give it whirl.
posted @ Friday, March 10, 2006 9:07 PM