Geeks With Blogs

News Questions, comments? Email me at "mb thomas [@] gmail . c o m" (remove the spaces.)
Michael Thomas asp.net and c#

I am currently working or a project that, like many projects has many sets of code values. Some examples of code values in the project that I am working on would be Call Results, Degree Types, or Genders. On the UI of this project these are (generally) represented as drop down lists. In order to create a more consistent development environment, I decided to create a CodeDropDownList. This is a custom control, extending a System.Web.UI.WebControls.DropDownList that has one extra parameter: CodeName. The goal is to be able to do something like <mike:CodeDropDownList runat=”server” id=”ddlGender” CodeName=”Gender” /> and have the drop down list auto populated with (“Male”, “Female”).

There are a few issues that you need to be aware of:

  1. Where to put your data loading code.
  2. How to cause the “DataBind” method to be called when it should be called.
  3. Performance improvements.

Where to load the data

The ideal time to load the data, is right before the list is DataBound. I accomplish this by overriding the DataBind method of the DropDownList with something like:

 public override void DataBind() {
    using (SqlConnection conn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=TestingAjax;Integrated Security=True;"))
    {
        conn.Open();
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = "code_gender_select";
            cmd.CommandType = CommandType.StoredProcedure;

            
            using (SqlDataReader dtr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                this.DataSource = dtr;
                this.DataTextField = "GenderName";
                this.DataValueField = "GenderId";

                base.DataBind();
            }
        }
    }   
}

I have changed this to use generic ADO.NET database access rather than any framework so that his will be as easy to understand (and dependant free) as possible.

The problem with this is that it does not work. When a page loads with this custom control on it the control is not databound, unless it is inside another databound control.

How to get this to work correctl

To resolve this issue I used the Reflector and looked at what was causing this problem. The method at fault ended up being the EnsureDataBound() method in the BaseDataList class.

 protected void EnsureDataBound() {
      try
      {
            this._throwOnDataPropertyChange = true;
            if (this.RequiresDataBinding && (this.DataSourceID.Length > 0))
            {
                  this.DataBind();
            }
      }
      finally
      {
            this._throwOnDataPropertyChange = false;
      }
}

According to Microsoft, the EnsureDataBound method does the following: “Verifies that the data listing control requires data binding and that a valid data source control is specified before calling the DataBind method.” We can change this method to the following:

 protected override void EnsureDataBound() {
    try
    {
        if (this.RequiresDataBinding)
        {
            this.DataBind();
        }
    }
    catch { }
}

This small change makes everything work great.

Performance Improvements

These code tables represent relatively static data, so a huge performance boost can be gained by caching the data from the database. The following methods are realtivley self explanatory. I use the following class to help cache the data:

 public class DropDownListItm {
    private string _Text;
    public string Text
    {
        get {return _Text;}
    }

    private string _Value;
    public string Value
    {
        get {return _Value;}
    }

    public DropDownListItm(string text, string value)
    {
        _Text=text;
        _Value=value;
    }
}

A new method that encapsulates the database access:

 public IEnumerable GetDataFromDB() {
    List<DropDownListItm> lItems = new List<DropDownListItm>();
    using (SqlConnection conn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=TestingAjax;Integrated Security=True;"))
    {
        conn.Open();
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = "code_gender_select";
            cmd.CommandType = CommandType.StoredProcedure;

            
            using (SqlDataReader dtr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                while(dtr.Read())
                {
                    lItems.Add(new DropDownListItm(dtr["GenderName"].ToString(), dtr["GenderId"].ToString()));
                }
            }
        }
    }

    return lItems; }

A new method for managing the caching:

 public IEnumerable GetDataSource() {
    string key = "CodeDrowDownListTest_" + CodeName;

    object item = Page.Cache.Get(key);
    if (item == null)
    {
        item = GetDataFromDB();
        Page.Cache.Insert(key, item, null, System.DateTime.UtcNow.AddHours(1), System.Web.Caching.Cache.NoSlidingExpiration);
    }

    return (IEnumerable)item; }

And finally, our DataBind() method:

 public override void DataBind() {
    this.DataSource = GetDataSource();
    this.DataTextField = "Text";
    this.DataValueField = "Value";
    
    base.DataBind();
}

All these methods and classes exist in the our CodeDropDownList class.

Full class listing:

 using System; using System.Data; using System.Collections; using System.Collections.Generic; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls; using System.Data.SqlClient; using System.Diagnostics;

namespace MikeThomas.CustomControls {
    public class CodeDropDownList : DropDownList
    {
        public string CodeName
        {
            get
            {
                if (ViewState["CodeName"] != null)
                {
                    return ViewState["CodeName"].ToString();
                }
                else
                {
                    return String.Empty;
                }
            }

            set { ViewState["CodeName"] = value; }
        }

        protected override void EnsureDataBound()
        {
            try
            {
                if (this.RequiresDataBinding)
                {
                    this.DataBind();
                }
            }
            catch { }
        }

        public override void DataBind()
        {
            this.DataSource = GetDataSource();
            this.DataTextField = "Text";
            this.DataValueField = "Value";
            
            base.DataBind();
        }

        public IEnumerable GetDataSource()
        {
            string key = "CodeDrowDownListTest_" + CodeName;

            object item = Page.Cache.Get(key);
            if (item == null)
            {
                item = GetDataFromDB();
                Page.Cache.Insert(key, item, null, System.DateTime.UtcNow.AddHours(1), System.Web.Caching.Cache.NoSlidingExpiration);
            }

            return (IEnumerable)item;
        }

        public IEnumerable GetDataFromDB()
        {
            List<DropDownListItm> lItems = new List<DropDownListItm>();
            using (SqlConnection conn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=TestingAjax;Integrated Security=True;"))
            {
                conn.Open();
                using (SqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "code_gender_select";
                    cmd.CommandType = CommandType.StoredProcedure;

                    
                    using (SqlDataReader dtr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        while(dtr.Read())
                        {
                            lItems.Add(new DropDownListItm(dtr["GenderName"].ToString(), dtr["GenderId"].ToString()));
                        }
                    }
                }
            }

            return lItems;
        }

        public class DropDownListItm
        {
            private string _Text;
            public string Text
            {
                get {return _Text;}
            }

            private string _Value;
            public string Value
            {
                get {return _Value;}
            }

            public DropDownListItm(string text, string value)
            {
                _Text=text;
                _Value=value;
            }
        }
    }
}
Posted on Monday, January 15, 2007 9:22 PM asp.net | Back to top


Comments on this post: Custom auto-bound Drop Down List

# Custom auto-bound Drop Down List
Requesting Gravatar...
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Left by DotNetKicks.com on Jan 19, 2007 8:50 PM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
I've configured a custom dropdownlist in the past for projects as you have done above, but have run into design-time issues in the VS 2005 design view (throws error in GetDataFromDB). The control renders with an error (fine), but annoyingly, if you have a for i.e. "Select Item" default ListItem specified in the control at 0, it sporadically adds this item to your html until you have several <asp:ListItem... />'s in your actual html that you need to occasionally clear out (annoying).

I haven't spent much time nailing down why in order to fix, but was wondering if you had (or do you even care ;-).
Left by Ted Jardine on Jan 23, 2007 10:10 AM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
I haven't had this problem, I do very little work in the design-time side of things. However, in dissecting the dropdownlist control I remember seeing a block of code that was something to the effect of:
if(DesignTime)
{
don't do databind
}
else
{
do databind
}

I don't know if that helps, but it's all I have :)

-Michael Thomas
Left by Michael Thomas on Jan 23, 2007 6:36 PM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
hiya,

I like this control.ONe thing I find though.If I try to get access to the dropdowns items in the pageLoad event event of the page, it doesn't seem to have been databound yet..

Is there anyway to make this control databind earlier in the page lifecycle, so that I could get access to the dropdown's items in the page's pageLoad event?

many thanks,

yogi
Left by yogiberr on Mar 31, 2008 10:08 AM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
hi,

First, good job :) i realy like your "method for managing the caching" ^^

i found a little mistake on :
" string key = "CodeDrowDownListTest_" + CodeName;"

If you are trying to use a lot of "CodeDropDownList" on the same page, there are all be bind with the same datas :s

a simlpe simple solution :
" string key = "CodeDrowDownListTest_" + this.ID;"

Drio



Left by Drio on Jun 25, 2008 9:10 PM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
hi,

First, good job :) i realy like your "method for managing the caching" ^^

i found a little mistake on :
" string key = "CodeDrowDownListTest_" + CodeName;"

If you are trying to use a lot of "CodeDropDownList" on the same page, there are all be bind with the same datas :s

a simlpe simple solution :
" string key = "CodeDrowDownListTest_" + this.ID;"

Drio



Left by Drio on Jun 25, 2008 9:10 PM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
hi,

First, good job :) i realy like your "method for managing the caching" ^^

i found a little mistake on :
" string key = "CodeDrowDownListTest_" + CodeName;"

If you are trying to use a lot of "CodeDropDownList" on the same page, there are all be bind with the same datas :s

a simlpe simple solution :
" string key = "CodeDrowDownListTest_" + this.ID;"

Drio



Left by Drio on Jun 25, 2008 9:10 PM

# re: Custom auto-bound Drop Down List
Requesting Gravatar...
Nice, but all properties that deal with the data (datasource, datextfield) don't make sense if you are performing the data binding internally.

A possible compromise is a private dropdown control- then you can expose only the properties that make sense.
Left by jguzman on Nov 25, 2008 9:14 AM

Your comment:
 (will show your gravatar)


Copyright © Michael Thomas | Powered by: GeeksWithBlogs.net | Join free