Blog from Saravanan Arumugam

Let us talk about Technologies

Learn data binding with .Net objects


Alternate Title: Custom HTML Helper for rendering Grid in MVC 3

I just wanted to give this blog an alternate title since this blog is going to do both.

 

Binding

There are a lot of bindable controls out there in almost all the .Net technologies, ASP.Net, WinForms, MVC, XAML/Silverlight.

I am not trying to talk about the controls that support binding. I am here to talk about how the controls do the binding, in other words, how do they recognize your data source and pull data from various data sources.

In Asp.Net, we typically enable the binding by writing the following code. (Winform would also have the similar step except that you won’t trigger DataBind() method)

SourceGrid.DataSource = myTable;
SourceGrid.DataBind();

Note that DataSource is a property in the control and it accepts the object type, which means it would accept (without compile time error). But during the runtime, type of the assigned object is evaluated to check if the object implements one of the following interfaces.

IDataSource

IListSource

IEnumerable

 

Types derived from IDataSource

Among these, IDataSource is a special one dedicated for ASP.Net. Following are the controls directly derived from IDataSource.

DataSourceControl

EntityDataSource

LinqDataSource

QueryableDataSource

SiteMapDataSource

and XmlDataSource

The meaning of the list is that we can assign an object of any of the above types as data source to a Bindable control.

 

Types derived from IListSource

IListSource and IEnumerable are common types of common bindable objects in any of the .Net technologies.

Following are the types derived from IListSource.

DataSet

DataSourceControl

DataTable

EntiryCollection<TEntity>

EntitySet<TEntity>

ObjectQuery

ObjectQuery<T>

ObjectResult

SiteMapDataSource

Table<TEntity>

XmlDataSource

 

Types derived from IEnumerable

IEnumerable is the simplest implementation needed to hold a collection of data. There are 100s of types derived from IEnumerable, so this section would not be sufficient to list them all. However I have listed a few of them here.

Array

Collection<T>

String

IEnumerable<T>

CollectionBase

DataKeyArray

DataView

IList

List<T>

etc.

Note, this is not the complete list.

Also note that String is an IEnumerable, which means it’s a collection of characters.

IEnumerable<T> is the generic version of IEnumerable, so anything that is derived from IEnumerable<T> would also come under the IEnumerable implementers.

 

Methods of Binding

In this blog I am going to explain the methods of binding with IEnumerable and IListSource. The binding methods are used to implement a custom MVC 3 Html helper for rendering a Grid from a bindable datasource.

 

Binding with IEnumerable

The only member of IEnumerable is GetEnumerator() method (Let’s not consider the extension methods). IEnumerator doesn’t have any other property or method within it.

IEnumerable.GetEnumerator()

The Enumerator on the other hand is an Interface IEnumerator. This has 3 members.

IEnumerator.Current

IEnumerator.MoveNext()

IEnumerator.Reset()

MoveNext() method moves the data pointer to the next available data. If the pointer movement is successful, the method returns true.

Current property gets you the object at the current data pointer.

Reset() method brings to pointer back to the first position.

IEnumerable enumerable = new[] { 4, 3, 5, 6, 7, 2, 6 };
IEnumerator enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    //Work with the item here
}

Code shown above is an example implementation of IEnumerable.

The same operation can be implemented simply by using foreach loop.

IEnumerable enumerable = new[] { 4, 3, 5, 6, 7, 2, 6 };
foreach (var item in enumerable)
{     //Work with item here
}

foreach loop internally gathers the Enumerator, resets the pointer to the starting position and does the same steps in the previous example.

Once we get the item, it is still not completely ready as data. The item inside the enumeration may be a simple type like integer, character, string. Or it could be a complex type like a structure or a class.

So once the item is received, check for its type. If it is a simple type display it as such. If it is complex type, use the reflection to get the list of Properties and its values.

if (data is IEnumerable)
{
    IEnumerator enumerator = ((IEnumerable)data).GetEnumerator();
    //To ensure that the data has atleast one item in it.
    if (enumerator.MoveNext())
    {
        gridTag.Append("<table>");
 
        if (enumerator.Current is ValueType||enumerator.Current is string)
        {
            gridTag.Append("<tr><th>Item</th></tr>");
            foreach (var item in (IEnumerable)data)
            {
                gridTag.Append(string.Format("<tr><td>{0}</td></tr>", item));
            }
        }
        else//It’s a complex type, so cannot 
        {
            gridTag.Append("<tr>");
 
            foreach (PropertyInfo info in enumerator.Current.GetType()
                                            .GetProperties())
            {
                string displayName = string.Empty;
                object[] attributes = info.GetCustomAttributes(
                                typeof(DisplayNameAttribute), 
                                false);
 
                if (attributes.Length > 0)
                    displayName = ((DisplayNameAttribute)attributes[0])
                                        .DisplayName;
                else
                    displayName = info.Name;
 
 
                gridTag.Append(string.Format("<th>{0}</th>", displayName));
            }
            gridTag.Append("</tr>");
 
            foreach (var item in (IEnumerable)data)
            {
                gridTag.Append("<tr>");
                foreach (PropertyInfo info in item.GetType().GetProperties())
                {
                    gridTag.Append(string.Format("<td>{0}</td>", 
                        info.GetValue(item, null)));
                }
                gridTag.Append("</tr>");
            }
        }
        gridTag.Append("</table>");
    }
}

Step 1: In the Html.Grid helper class, I used the code above to handle the IEnumerable. Use the IEnumerator.MoveNext() method to ensure that there is at least one record to display.

Step 2: As the next step, check if the record is a value type or String (String is not a value type and is itself derived from IEnumerable). If so, simply iterate though the IEnumerable using foreach and display the values.

Step 3: If the record contained in IEnumerable is a complex type, use the reflection to access its public properties. Loop through the properties list to frame the header for the grid. Then iterate through the actual IEnumerable and use the PropertyInfo.GetValue() method to get the value contained in the record.

Step 4: Note that while framing the header, you would want to display the user friendly name rather than the actual property name. DisplayNameAttribute can be used to facilitate friendly names to regular properties. So while rendering the header, look for the DisplayNameAttribute for the property before using the regular property name. Use the PropertyInfo.GetCustomAttributes() method to look for the availability of DisplayNameAttribute.

 

Binding with IListSource

Unlike IEnumerable, IListSource can’t be directly iterated. IListSource comes with GetList() method which returns IList object.This IList object can be iterated to access each records. In addition to holding a flat list of items (like DataTable), IListSource can hold a list of lists (DataSet containing a list of DataTables) and hierarchical data (Like XML).

if (data is IListSource)
{
    gridTag.Append(RenderGrid((ITypedList)
                 ((IListSource)data).GetList()));
}
internal static string RenderGrid(ITypedList list)
{
    StringBuilder htmlString = new StringBuilder();
    PropertyDescriptorCollection propDescriptors 
        = list.GetItemProperties(null);
 
    IList regularList = (IList)list;
    bool hasDeeperLevel = false;
 
    //Iteration to check if the list has any deeper levels
    foreach (var l in (IList)list)
    {
        foreach (PropertyDescriptor pd in propDescriptors)
        {
            var item = pd.GetValue(l);
            if (item is ITypedList)
            {
                hasDeeperLevel = true;
                htmlString.Append(RenderGrid((ITypedList)item));
            }
        }
    }
    if (hasDeeperLevel) return htmlString.ToString();
 
    if (propDescriptors.Count > 0)
    {
        htmlString.Append("<table><tr>");
        foreach (PropertyDescriptor pd in propDescriptors)
        {
            htmlString.Append("<th>" + pd.DisplayName + "</th>");
        }
        htmlString.Append("</tr>");
        foreach (var item in regularList)
        {
            htmlString.Append("<tr>");
            foreach (PropertyDescriptor property in propDescriptors)
            {
                htmlString.Append("<td>" + 
                    property.GetValue(item) + "</td>");
            }
            htmlString.Append("</tr>");
        }
 
        htmlString.Append("</table><br/>");
    }
    return htmlString.ToString();
}

Step 1: Once you get the IList using IListSource.GetList() method, cast it to ITypedList. We would use both the IList and ITypedList while retrieving data from them. IList provides the GetEnumerator() method which is useful for iteration, whereas the ITypedList provides GetItemProperties() method.

Step 2: GetItemProperties returns a collection of PropertyDescriptors. Check to see if the item has another ITypedList inside it. Identifying the inner list can be done with the use of PropertyDescriptor.GetValue() method. If PropertyDescriptor says that there’s an inner list, do the rendering process for the inner list. This has to go recursively.

Step 3: When you understand the list is not a ITypedList by itself, start looking for the data to render. Iterating through PropertyDescriptorCollection would help us do that.

Step 4: Loop through the PropertyDescriptorCollection and use PropertyDescriptor.DisplayName to render the Grid header.

Step 5: Loop through the PropertyDescriptorCollection again to get the content of each row. PropertyDescriptor.GetValue() helps in getting the data to render.

 

Complete Code Listing

Limitations: This extension accepts IEnumerable and IListSource only. It doesn’t accept IDataSource as ASP.Net controls do. Another limitation is that the IListSource is tested with DataTables and DataSets, its is not tested with entities and hierarchical data.

List 1: Grid extension method

The Custom helpers are actually the extension methods return on HtmlHelper class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections;
using System.Text;
using System.ComponentModel;
using System.Reflection;
 
namespace Infrastructure
{
    public static class CustomHelpers
    {
        public static MvcHtmlString Grid(this HtmlHelper helper, object data)
        {
            if (!(data is IEnumerable) && !(data is IListSource))
                throw new InvalidEnumArgumentException("The argument data 
                 has to be either IEnumerable or IListSource");
 
            StringBuilder gridTag = new StringBuilder();
 
            if (data is IListSource)
            {
                gridTag.Append(RenderGrid((ITypedList)
                          ((IListSource)data).GetList()));
            }
            else
                if (data is IEnumerable)
                {
                    IEnumerator enumerator = 
                          ((IEnumerable)data).GetEnumerator();
                    //To ensure that the data has atleast one item in it.
                    if (enumerator.MoveNext())
                    {
                        gridTag.Append("<table>");
 
                        if (enumerator.Current is ValueType || 
                                   enumerator.Current is string)
                        {
                            gridTag.Append("<tr><th>Item</th></tr>");
                            foreach (var item in (IEnumerable)data)
                            {
                                gridTag.Append(
                                 string.Format("<tr><td>{0}</td></tr>", item));
                            }
                        }
                        else
                        {
                            gridTag.Append("<tr>");
 
                            foreach (PropertyInfo info in 
                                            enumerator.Current.GetType()
                                          .GetProperties())
                            {
                                string displayName = string.Empty;
                                object[] attributes = 
                                            info.GetCustomAttributes(
                                          typeof(DisplayNameAttribute),
                                          false);
 
                                if (attributes.Length > 0)
                                    displayName = 
                                          ((DisplayNameAttribute)attributes[0])
                                          .DisplayName;
                                else
                                    displayName = info.Name;
 
 
                                gridTag.Append(
                                  string.Format("<th>{0}</th>", displayName));
                            }
                            gridTag.Append("</tr>");
 
                            foreach (var item in (IEnumerable)data)
                            {
                                gridTag.Append("<tr>");
                                foreach (PropertyInfo info in 
                                            item.GetType().GetProperties())
                                {
                                    gridTag.Append(string.Format("<td>{0}</td>",
                                        info.GetValue(item, null)));
                                }
                                gridTag.Append("</tr>");
                            }
                        }
                        gridTag.Append("</table>");
                    }
                }
 
            return MvcHtmlString.Create(gridTag.ToString());
        }
 
        internal static string RenderGrid(ITypedList list)
        {
            StringBuilder htmlString = new StringBuilder();
            PropertyDescriptorCollection propDescriptors
                = list.GetItemProperties(null);
 
            IList regularList = (IList)list;
            bool hasDeeperLevel = false;
 
            foreach (var l in (IList)list)
            {
                foreach (PropertyDescriptor pd in propDescriptors)
                {
                    var item = pd.GetValue(l);
                    if (item is ITypedList)
                    {
                        hasDeeperLevel = true;
                        htmlString.Append(RenderGrid((ITypedList)item));
                    }
                }
            }
            if (hasDeeperLevel) return htmlString.ToString();
 
            if (propDescriptors.Count > 0)
            {
                htmlString.Append("<table><tr>");
                foreach (PropertyDescriptor pd in propDescriptors)
                {
                    htmlString.Append("<th>" + pd.DisplayName + "</th>");
                }
                htmlString.Append("</tr>");
                foreach (var item in regularList)
                {
                    htmlString.Append("<tr>");
                    foreach (PropertyDescriptor property in propDescriptors)
                    {
                        htmlString.Append("<td>" +
                            property.GetValue(item) + "</td>");
                    }
                    htmlString.Append("</tr>");
                }
 
                htmlString.Append("</table><br/>");
            }
            return htmlString.ToString();
        }
    }
}

Listing 2: Model Class

public class LicenseInfo
{
    static LicenseInfo()
    {
        compInfo = new List<CompanyInfo>();
        compInfo.Add(new CompanyInfo()
        {
            ID = 1,
            Company = new Company()
            {
                CompanyID = 1,
                CompanyName = "Microsoft",
                URL = "www.Microsoft.com"
            },
            Address = "Redmond, WA - 98052",
            Rating = 5,
            HasBranchesOverseas = false
 
        });
 
        compInfo.Add(new CompanyInfo()
        {
            ID = 2,
            Company = new Company()
            {
                CompanyID = 2,
                CompanyName = "Apple",
                URL = "www.Apple.com"
            },
            Address = "1 Infinite Loop Cupertino, CA - 95014",
            Rating = 4,
            HasBranchesOverseas = true
        });
    }
 
    public IList<CompanyInfo> companyInfo { get { return compInfo; } }
    static IList<CompanyInfo> compInfo;
}
 
public class CompanyInfo
{
    [DisplayName("Company Identity")]
    public int ID { get; set; }
    public Company Company { get; set; }
    public string Address { get; set; }
    public int Rating { get; set; }
 
    [DisplayName("Has Branches Overseas")]
    public bool HasBranchesOverseas { get; set; }        
}
 
public class Company
{
    public int CompanyID { get; set; }
    public string CompanyName { get; set; }
    public string URL { get; set; }
 
    public override string ToString()
    {
        return CompanyName;
    }
}
 
public class Samples
{
    public DataTable SampleTable
    {
        get
        {
            return GetMeTable();
        }
    }
    public DataSet SampleDataSet
    {
        get
        {
            return GetMeDataSet(SampleTable);
        }
    }
 
    private DataTable GetMeTable()
    {
        DataTable myTable = new DataTable("MyTable");
        myTable.Columns.Add("ID", typeof(int));
        myTable.Columns.Add("Name", typeof(string));
        myTable.Columns.Add("Salary", typeof(double));
        DataRow row = myTable.NewRow();
        row["ID"] = 1;
        row["Name"] = "Saravanan";
        row["Salary"] = 1275.50;
        myTable.Rows.Add(row);
 
        row = myTable.NewRow();
        row["ID"] = 2;
        row["Name"] = "Joe";
        row["Salary"] = 2255.00;
        myTable.Rows.Add(row);
 
        row = myTable.NewRow();
        row["ID"] = 3;
        row["Name"] = "Peter";
        row["Salary"] = 1500.50;
        myTable.Rows.Add(row);
        return myTable;
    }
 
    private DataSet GetMeDataSet(DataTable myTable)
    {
        DataRow row = myTable.NewRow();
        row["ID"] = 4;
        row["Name"] = "John";
        row["Salary"] = 3500.50;
        myTable.Rows.Add(row);
 
        row = myTable.NewRow();
        row["ID"] = 5;
        row["Name"] = "Sathish";
        row["Salary"] = 8500.50;
        myTable.Rows.Add(row);
 
        DataSet ds = new DataSet("My Data Set");
        ds.Tables.Add(myTable);
 
        DataTable addressTable = new DataTable("Address Table");
        addressTable.Columns.Add("StaffID", typeof(int));
        addressTable.Columns.Add("Street");
        addressTable.Columns.Add("Apartment");
        addressTable.Columns.Add("City");
        addressTable.Columns.Add("State");
        addressTable.Columns.Add("Country");
        addressTable.Columns.Add("Zip");
 
        row = addressTable.NewRow();
        row["StaffID"] = 1;
        row["Street"] = "Street 1";
        row["Apartment"] = "101";
        row["City"] = "Omaha";
        row["State"] = "Nebraska";
        row["Country"] = "USA";
        row["Zip"] = "68000";
        addressTable.Rows.Add(row);
 
        row = addressTable.NewRow();
        row["StaffID"] = 2;
        row["Street"] = "Street 2";
        row["Apartment"] = "201";
        row["City"] = "Tulsa";
        row["State"] = "Oklahoma";
        row["Country"] = "USA";
        row["Zip"] = "56000";
        addressTable.Rows.Add(row);
        ds.Tables.Add(addressTable);
 
        return ds;
    }
}

Listing 3: Controller Action

public ActionResult Index(
{
    Samples s = new Samples();
    ViewBag.SampleTable = s.SampleTable;
    ViewBag.SampleDataSet = s.SampleDataSet;
 
    var model = licenseInfo.companyInfo.ToList();
    return View(model.ToArray());
}

Listing 4: View

@using Infrastructure
@using System.Collections
@using System.Data
@model IEnumerable<LicenseMVC.Models.CompanyInfo>
@{
    ViewBag.Title = "Grid Helper";
    DataTable sampleTable = ViewBag.SampleTable;
    DataSet sampleDS = ViewBag.SampleDataSet;    
}
<h2>
    Grid Helper</h2>
<b>Output from an Array</b><br />
@Html.Grid(new[] { 5, 6, 7, 6, 8, 6, 4, 1, 36, 58 })
<br />
 
<b>Output from a simple IEnumerable</b><br />
@Html.Grid("Albert Einstein")
<br />
 
<b>Output from a list</b><br />
@Html.Grid(new List<string>{"Milli", "Micro", 
         "Nano", "Pico", "Femto"})
<br />
 
<b>Output from generic IEnumerable(Complex type)</b><br />
@Html.Grid(Model)
<br />
 
<b>Output from a DataTable</b><br />
@Html.Grid(sampleTable)
<br />
 
<b>Output from a DataSet</b><br />
@Html.Grid(sampleDS)
 
@section footer
{
    <text>© 2012 Saravanan Arumugam. All rights reserved.</text>
}

Output

Grid Helper

Output from an Array

Item
5
6
7
6
8
6
4
1
36
58

Output from a simple IEnumerable

Item
A
l
b
e
r
t
 
E
i
n
s
t
e
i
n

Output from a list

Item
Milli
Micro
Nano
Pico
Femto

Output from generic IEnumerable(Complex type)

Company Identity Company Address Rating Has Branches Overseas
1 Microsoft Redmond, WA – 98052 5 False
2 Apple 1 Infinite Loop Cupertino, CA – 95014 4 True

Output from a DataTable

ID Name Salary
1 Saravanan 1275.5
2 Joe 2255
3 Peter 1500.5

Output from a DataSet

ID Name Salary
1 Saravanan 1275.5
2 Joe 2255
3 Peter 1500.5
4 John 3500.5
5 Sathish 8500.5

StaffID Street Apartment City State Country Zip
1 Street 1 101 Omaha Nebraska USA 68000
2 Street 2 201 Tulsa Oklahoma USA 56000

© 2012 Saravanan Arumugam. All rights reserved.
Advertisements

One response to “Learn data binding with .Net objects

  1. garfbradaz March 2, 2012 at 5:56 am

    This is an excellent and well written article AND ive added it to my favs to read in more detail tonight. Thank you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: