Blog Home  Home Feed your aggregator (RSS 2.0)  
Binding a DropDownList to multiple properties and to nested properties - Manuel Abadia's ASP.NET stuff
 
# Tuesday, 23 December 2008

All controls that inherit from ListControl (BulletedList, CheckBoxList, DropDownList, ListBox and RadioButtonList) have some annoying behavior in common:

You can only bind the control to a single property using the DataTextField property. For example, if you have a Customer class with properties Id, Name, Surname, etc and you want to show the full name of a customer in any of the ListControls, you have several options:

  1. When you get the data from the database, return another column with the name and surname.
  2. Create a property (f.e. FullName) that returns what you want to show.
  3. Use a DataSet and add a computed column.
  4. Manually add ListItems with the appropriate values performing the data bind manually.
  5. Create a simple class that will be bound (instead of the original one) just to fix the problem.

The truth is that I don’t like any of those options, all of them are hacks.

For nested properties you have the similar problems. You can bind to Id or Name, but you can’t bind to Address.City or Address.Zip.

Today I faced this problem again and I decided to investigate a more innovative solution. The problem itself is in the controls that inherit from ListControl that are very strict in the binding options. So I took a look of how the controls perform the binding to see if I could do anything to overcome those limitations. At first sight I thought I was loosing my time but after a deeper study I found that it was amazingly easy to fix those problems in a very sleek way.

The data binding of the controls is performed in the PerformDataBinding method. The code at ListControl.PerformDataBinding uses DataBinder.GetPropertyValue to retrieve the value to show in the control if you set the DataTextField property. However, DataBinder.GetPropertyValue doesn’t handle nested properties. DataBinder.Eval is a better choice because it does handle nested properties. So using it we solve one problem. The other problem can be solved easily as well. As we have two properties that control how the Text of each ListItem will be extracted and shown from the data source, with a bit more of effort we can handle the new functionality. I have come up with a very intuitive solution:

  • DataTextField allows one or more properties (that can be nested if needed) separated by commas.
  • DataTextFormatString can be used to control the formatting of the items, using {0}, {1}, {2}, {3}… as the value of the properties specified with DataTextField.

The code that performs the data binding is:

/// <summary>Binds the data to the control.</summary>
/// <param name="ctrl">control to bind.</param>
/// <param name="dataSource">data to bind to the control.</param>
public static void PerformDataBinding(ListControl ctrl, IEnumerable dataSource)
{
    // resets the selected item
    ctrl.SelectedIndex = -1;
    ctrl.SelectedValue = null;

    if (dataSource != null) {
        bool fieldInfoSet = false;
        bool dataTextFormatStringSet = false;

        // checks if we have to clear the existing items
        if (!ctrl.AppendDataBoundItems) {
            ctrl.Items.Clear();
        }

        // if the data source is a collection, sets the capacity
        ICollection soureceCollection = dataSource as ICollection;
        if (soureceCollection != null) {
            ctrl.Items.Capacity = soureceCollection.Count + ctrl.Items.Count;
        }

        // save if the data text field of data value field has been set
        if ((ctrl.DataTextField.Length != 0) || (ctrl.DataValueField.Length != 0)) {
            fieldInfoSet = true;
        }

        // save if the data text format string has been set
        if (ctrl.DataTextFormatString.Length != 0) {
            dataTextFormatStringSet = true;
        }

        // iterates through the data source creating the ListItems
        foreach (object obj in dataSource) {
            ListItem item = new ListItem();

            if (fieldInfoSet) {
                if (ctrl.DataTextField.Length > 0) {
                    if (ctrl.DataTextField.IndexOf(',') == -1) {
                        item.Text = DataBinder.Eval(obj, ctrl.DataTextField, ctrl.DataTextFormatString);
                    } else {
                        // if the DataTextField property has a list of fields, get them to create the text of the item
                        string[] fields = ctrl.DataTextField.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                        object[] values = new object[fields.Length];

                        for (int i = 0; i < fields.Length; i++) {
                            string field = fields[i];
                            values[i] = DataBinder.Eval(obj, field);
                        }

                        item.Text = String.Format(ctrl.DataTextFormatString, values);
                    }
                }
                if (ctrl.DataValueField.Length > 0) {
                    item.Value = DataBinder.Eval(obj, ctrl.DataValueField, null);
                }
            } else {
                if (dataTextFormatStringSet) {
                    item.Text = string.Format(CultureInfo.CurrentCulture, ctrl.DataTextFormatString, new object[] { obj });
                } else {
                    item.Text = obj.ToString();
                }
                item.Value = obj.ToString();
            }
           
            ctrl.Items.Add(item);
        }
    }
}

 

As we need to call this data binding code instead of the one from ListControl I needed to subclass all controls that inherit from ListControl in order to fix them. So now I have BulletedListEx, CheckBoxListEx, DropDownListEx, ListBoxEx and RadioButtonListEx.

I have created a simple example showing how to use them. Imagine that an Item can be supplied by a set of suppliers, and each supplier sells the item with a different price. The following image shows the properties of the 3 entities of the sample:

I have created a web page with a DropDownList with some items, and when you select an item in the DropDownList you will see a ListBox with the all suppliers of that item with the associated price for the item. Notice how I show the Id and the Name in the DropDownList and how I show the price, supplier Id and supplier name in the ListBox:

The ASPX of the page follows:

<body>
    <form id="form1" runat="server">
    <div>
    </div>
        Choose an item to see the suppliers:<br />
        Items:<br />
        <manu:DropDownListEx ID="DropDownListEx1" runat="server" AutoPostBack="True" Width="300px"
            DataTextField="Id, Name"
            DataTextFormatString="[{0}] - {1}"
            DataValueField="Id"
            OnSelectedIndexChanged="DropDownListEx1_SelectedIndexChanged" AppendDataBoundItems="True">
            <asp:ListItem Selected="True" Value="0">Select an Item</asp:ListItem>
        </manu:DropDownListEx><br />
       
        Price and suppliers:<br />
       
        <manu:ListBoxEx ID="ListBoxEx1" runat="server" Width="300px"
            DataTextField="Price, Supplier.Id, Supplier.Name"
            DataTextFormatString="{0} - [{1}] {2}"
            DataValueField="Supplier.Id">
        </manu:ListBoxEx>
    </form>
</body>

 

As you can see, the solution is really clean and elegant, and backward compatible with the current controls that inherit from ListControl.

That’s all for now,
Merry christmas!

ListControlsEx_bin.zip (6.53 KB)

ListControlsEx_sample.zip (33.75 KB)
Tuesday, 23 December 2008 00:35:20 (Romance Standard Time, UTC+01:00)  #    Comments [2]   ASP.NET | Microsoft .NET Framework  | 
Thursday, 28 May 2009 01:03:17 (Romance Daylight Time, UTC+02:00)
This is excellent. Exactly what I was looking for!
Thursday, 17 October 2013 11:56:12 (Romance Daylight Time, UTC+02:00)
Thank you!
John
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

[Captcha]Enter the code shown (prevents robots):

Live Comment Preview
Copyright © 2017 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.