ASP.Net GridView Implementing Cascading DropDownList on Edit Mode

Introduction

This question has been asked many times in forums (such as in http://forums.asp.net) and there definitely are already many solutions available. Most of the examples from the web use DataSource controls (for example SqlDataSource, ObjectDataSource, and so on) to implement a cascading DropDownList in a GridView and I can't seem to find a formal example that shows how to implement it without using DataSource controls.

Note

Before you proceed, make sure you already understand the basics of using the GridView control and how to bind it with data since I will not include the details of doing edit, update or delete scenarios for this exercise.

Scenario

As a recap, a Cascading DropDownList enables a common scenario in which the contents of one list depends on the selection of another list. In this demo we will show a list of Product orders in a GridView and when the user edits the row, we will present the user with a DropDownList containing the list of available makes of products. Once they have made their selection, we will populate the second DropDownList with the available Models for the specific make they've chosen. And once they have chosen the model from the second DropDownList we will then update the Price field for that corresponding model.

ASPX Markup

This article will have a look at how to possibly do this. To get started let's setup our HTML .aspx markup. For the simplicity of this demo, I just set it up like this.

  1. <asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="false" DataKeyNames="ProductOrderID"  
  2.                     onrowcancelingedit="gvProducts_RowCancelingEdit"   
  3.                     onrowediting="gvProducts_RowEditing"  
  4.                     onrowupdating="gvProducts_RowUpdating"   
  5.                     onrowdatabound="gvProducts_RowDataBound">  
  6.         <Columns>  
  7.             <asp:BoundField DataField="ProductOrderID"  ReadOnly="true"/>  
  8.             <asp:TemplateField HeaderText="Make">  
  9.                 <ItemTemplate>  
  10.                        <asp:Label ID="lblMake" runat="server" Text='<%# Bind("Make") %>' />  
  11.                 </ItemTemplate>  
  12.                 <EditItemTemplate>  
  13.                     <asp:DropDownList ID="ddlProductMake" runat="server"  
  14.                                       OnSelectedIndexChanged="ddlProductMake_SelectedIndexChanged"  
  15.                                       AutoPostBack="true">  
  16.                     </asp:DropDownList>  
  17.                 </EditItemTemplate>  
  18.             </asp:TemplateField>  
  19.             <asp:TemplateField HeaderText="Model">  
  20.                 <ItemTemplate>  
  21.                      <asp:Label ID="lblModel" runat="server" Text='<%# Bind("Model") %>' />  
  22.                 </ItemTemplate>  
  23.                 <EditItemTemplate>  
  24.                     <asp:DropDownList ID="ddlProductModel" runat="server"  
  25.                                       OnSelectedIndexChanged="ddlProductModel_SelectedIndexChanged"  
  26.                                       AutoPostBack="true">  
  27.                     </asp:DropDownList>  
  28.                 </EditItemTemplate>  
  29.            </asp:TemplateField>  
  30.            <asp:BoundField DataField="Price" HeaderText="Price" ReadOnly="True" DataFormatString="{0:c}"/>  
  31.            <asp:BoundField DataField="Quantity" HeaderText="Quantity"/>  
  32.            <asp:CommandField ShowEditButton="True" />  
  33.         </Columns>  
  34. </asp:GridView> 
The markup above is composed of 6 columns of various types of control fields. The first column is a BoundField that holds the ProductOrderID field. You may notice that we set this field as the DataKeyName in the GridView so we can easily reference the ID of the row later on. The 2nd and 3rd columns are TemplateFields that contain a Label control within an <ItemTemplate> and a DropDownList control within an <EditItemTemplate>. This means that on a read-only state the Label will be displayed and on an edit-state the DropDownList will be displayed to allow users to modify the selection of their choice. Note that you need to set AutoPostBack to TRUE for the DropDownList to trigger the SelectedIndexChanged event. The 4th and 5th columns are BoundFields that hold the Price and Quantity fields. You may notice that the Price field has an attribute DataFormatString="{0:c}" set to it. This will transform the value to a currency format when displayed in the browser. Finally, the last column is a CommandField with ShowEditButton enabled. This will generate the Edit link when the GridView is bound to data.

The DataSource

Just for the simplicity of this exercise, I didn't use a database and instead I've just created some classes that will hold some properties on it. Here are the class definitions.
  1. public class ProductMake  
  2. {  
  3.     public int ProductMakeID { getset; }  
  4.     public string Make { getset; }  
  5. }  
  6. public class ProductModel  
  7. {  
  8.     public int ProductModelID { getset; }  
  9.     public int ProductMakeID { getset; }  
  10.     public string Model { getset; }  
  11.     public decimal Price { getset; }  
  12. }  
  13. public class ProductOrder  
  14. {  
  15.     public int ProductOrderID { getset; }  
  16.     public int ProductMakeID { getset; }  
  17.     public int ProductModelID { getset; }  
  18.     public string Make { getset; }  
  19.     public string Model { getset; }  
  20.     public short Quantity { getset; }  
  21.     public decimal Price { getset; }  
Note that this is just an example to make this exercise work. You can always replace this with your DataTable, Entity objects, Custom classes and so on that holds your actual source of data.

The ProductMake class holds two main properties. This is where we store the list of makes of the product. The ProductModel class is where we store all the models for each make. The ProductOrder class is where we store the list of orders the user has chosen. Now let's go ahead and provide this class with some sample data. The following is the code blocks.
  1. private List<ProductOrder> GetUserProductOrder() {  
  2.           List<ProductOrder> orders = new List<ProductOrder>();  
  3.           ProductOrder po = new ProductOrder();  
  4.   
  5.           po.ProductOrderID = 1;  
  6.           po.ProductMakeID = 1;  
  7.           po.ProductModelID = 1;  
  8.           po.Make = "Apple";  
  9.           po.Model = "iPhone 4";  
  10.           po.Quantity = 2;  
  11.           po.Price = 499;  
  12.           orders.Add(po);  
  13.   
  14.           po = new ProductOrder();  
  15.           po.ProductOrderID = 2;  
  16.           po.ProductMakeID = 2;  
  17.           po.ProductModelID = 4;  
  18.           po.Make = "Samsung";  
  19.           po.Model = "Galaxy S2";  
  20.           po.Quantity = 1;  
  21.           po.Price = 449;  
  22.           orders.Add(po);  
  23.   
  24.           po = new ProductOrder();  
  25.           po.ProductOrderID = 3;  
  26.           po.ProductMakeID = 3;  
  27.           po.ProductModelID = 7;  
  28.           po.Make = "Nokia";  
  29.           po.Model = "Lumia";  
  30.           po.Quantity = 1;  
  31.           po.Price = 549;  
  32.           orders.Add(po);  
  33.   
  34.           return orders;  
  35.   
  36.       }  
  37.   
  38.       private List<ProductMake> GetProductMakes() {  
  39.   
  40.           List<ProductMake> products = new List<ProductMake>();  
  41.           ProductMake p = new ProductMake();  
  42.   
  43.           p.ProductMakeID = 1;  
  44.           p.Make = "Apple";  
  45.           products.Add(p);  
  46.   
  47.           p = new ProductMake();  
  48.           p.ProductMakeID = 2;  
  49.           p.Make = "Samsung";  
  50.           products.Add(p);  
  51.   
  52.           p = new ProductMake();  
  53.           p.ProductMakeID = 3;  
  54.           p.Make = "Nokia";  
  55.           products.Add(p);  
  56.   
  57.           return products;  
  58.       }  
  59.   
  60.       private List<ProductModel> GetProductModels() {  
  61.           List<ProductModel> productModels = new List<ProductModel>();  
  62.           ProductModel pm = new ProductModel();  
  63.   
  64.           pm.ProductMakeID = 1;  
  65.           pm.ProductModelID = 1;  
  66.           pm.Model = "iPhone 4";  
  67.           pm.Price = 499;  
  68.           productModels.Add(pm);  
  69.   
  70.           pm = new ProductModel();  
  71.           pm.ProductMakeID = 1;  
  72.           pm.ProductModelID = 2;  
  73.           pm.Model = "iPhone 4s";  
  74.           pm.Price = 599;  
  75.           productModels.Add(pm);  
  76.   
  77.           pm = new ProductModel();  
  78.           pm.ProductMakeID = 1;  
  79.           pm.ProductModelID = 3;  
  80.           pm.Model = "iPhone 5";  
  81.           pm.Price = 699;  
  82.           productModels.Add(pm);  
  83.   
  84.           pm = new ProductModel();  
  85.           pm.ProductMakeID = 2;  
  86.           pm.ProductModelID = 4;  
  87.           pm.Model = "Galaxy S2";  
  88.           pm.Price = 449;  
  89.           productModels.Add(pm);  
  90.   
  91.           pm = new ProductModel();  
  92.           pm.ProductMakeID = 2;  
  93.           pm.ProductModelID = 5;  
  94.           pm.Model = "Galaxy S3";  
  95.           pm.Price = 549;  
  96.           productModels.Add(pm);  
  97.   
  98.           pm = new ProductModel();  
  99.           pm.ProductMakeID = 2;  
  100.           pm.ProductModelID = 6;  
  101.           pm.Model = "Galaxy Note2";  
  102.           pm.Price = 619;  
  103.           productModels.Add(pm);  
  104.   
  105.           pm = new ProductModel();  
  106.           pm.ProductMakeID = 3;  
  107.           pm.ProductModelID = 7;  
  108.           pm.Model = "Nokia Lumia";  
  109.           pm.Price = 659;  
  110.           productModels.Add(pm);  
  111.   
  112.           return productModels;  
  113.   
  114.       }  
  115.   
  116.       private List<ProductModel> GetProductModelByMake(int productMakeID) {  
  117.           var models = (from p in GetProductModels()  
  118.                         where p.ProductMakeID == productMakeID  
  119.                         select p);  
  120.   
  121.           return models.ToList();  
  122.       }  
The GetUserProductOrder() fetches the list of orders. We will use this as our DataSource in the GridView later. The GetProductMakes() method gets all the available makes that, in this case, we just added 3 main items to. The GetProductModel() method gets all the available models for each make. The GetProductModelByMake() method gets the specific model item and its details are based on the ProductMakeID. This method uses the LINQ syntax to query the DataSource based on the parameter that was passed to it.

The Implementation

Now it looks as if we already have some sample source of data to work with. Now let's go ahead and do the highlight of this exercise (which is the implementation of the cascading dropdownlist). Here are the code blocks below.
  1.        private void BindGrid() 
  2.        {  
  3.            gvProducts.DataSource = GetUserProductOrder();  
  4.            gvProducts.DataBind();  
  5.        }  
  6.   
  7.        protected void gvProducts_RowEditing(object sender, GridViewEditEventArgs e) {  
  8.            gvProducts.EditIndex = e.NewEditIndex;  
  9.            BindGrid();  
  10.        }  
  11.   
  12.        protected void gvProducts_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e) {  
  13.            gvProducts.EditIndex = -1;  
  14.            BindGrid();  
  15.        }  
  16.   
  17.        protected void gvProducts_RowDataBound(object sender, GridViewRowEventArgs e) {  
  18.            if (e.Row.RowType == DataControlRowType.DataRow) {  
  19.                if ((e.Row.RowState & DataControlRowState.Edit) > 0) {  
  20.                    DropDownList ddlMake = (DropDownList)e.Row.FindControl("ddlProductMake");  
  21.                    ddlMake.DataSource = GetProductMakes();  
  22.                    ddlMake.DataValueField = "ProductMakeID";  
  23.                    ddlMake.DataTextField = "Make";  
  24.                    ddlMake.DataBind();  
  25.   
  26.                    ddlMake.SelectedValue = gvProducts.DataKeys[e.Row.RowIndex].Value.ToString();  
  27.   
  28.                    DropDownList ddlModel = (DropDownList)e.Row.FindControl("ddlProductModel");  
  29.                    ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value));  
  30.                    ddlModel.DataValueField = "ProductModelID";  
  31.                    ddlModel.DataTextField = "Model";  
  32.                    ddlModel.DataBind();  
  33.   
  34.                    ddlModel.SelectedValue = GetProductModelByMake(Convert.ToInt32(gvProducts.DataKeys[e.Row.RowIndex].Value))  
  35.                                             .FirstOrDefault().ProductModelID.ToString();  
  36.   
  37.                }  
  38.            }  
  39.        }  
  40.   
  41.        protected void ddlProductMake_SelectedIndexChanged(object sender, EventArgs e) {  
  42.            DropDownList ddlMake = (DropDownList)sender;  
  43.            GridViewRow row = (GridViewRow)ddlMake.NamingContainer;  
  44.            if (row != null) {  
  45.                if ((row.RowState & DataControlRowState.Edit) > 0) {  
  46.                    DropDownList ddlModel = (DropDownList)row.FindControl("ddlProductModel");  
  47.                    ddlModel.DataSource = GetProductModelByMake(Convert.ToInt32(ddlMake.SelectedValue));  
  48.                    ddlModel.DataValueField = "ProductModelID";  
  49.                    ddlModel.DataTextField = "Model";  
  50.                    ddlModel.DataBind();  
  51.                }  
  52.            }  
  53.        }  
  54.   
  55.        protected void ddlProductModel_SelectedIndexChanged(object sender, EventArgs e) {  
  56.            DropDownList ddlModel = (DropDownList)sender;  
  57.            GridViewRow row = (GridViewRow)ddlModel.NamingContainer;  
  58.            if (row != null) {  
  59.                if ((row.RowState & DataControlRowState.Edit) > 0) {  
  60.                    row.Cells[3].Text = string.Format("{0:C}", GetProductModels()  
  61.                                        .Where(o => o.ProductModelID == Convert.ToInt32(ddlModel.SelectedValue))  
  62.                                        .FirstOrDefault().Price);  
  63.                }  
  64.            }  
  65.        }  
The gvProducts_RowDataBound event is where we bind the DropDownList with the data from our DataSource. First we check the RowType to ensure that we are only manipulating the rows of type DataRow. Please note that a GridView is composed of several row types such as Header, DataRow, EmptyDataRow, Footer, Pager and Separator. The next line in that block is the critical part of the code and that is to determine the Edit state.

Accessing the controls from within <EditItemTemplate> is a bit tricky, especially if you are not really familiar with how the stuff works within a GridView. Equating the RowState to DataControlState.Edit isn't really accurate and you might get an exception when doing so. The RowState property is a bitwise combination. Therefore, the RowState could indicate that you are in the Edit state and the Alternate state. Hence, you cannot do a simple equality check when you are in Edit mode. Instead you must do something like this.
  1. if ((e.Row.RowState & DataControlRowState.Edit) > 0)   
  2. {  
  3. //do your stuff here  
  4. }  
We use the bitwise “&” operator to determine if the GridView is in Edit mode and check the result if its greater than zero. For details about the Bitwise operator see: Bitwise Operators in C#.

Once we've manage to determine the edit-state then we can now begin accessing the controls using the FindControl() method and bind it with the corresponding DataSources. If you notice, I've set the SelectedValue for the ddlMake and ddlModel DropDownLists so that by the time the user clicks edit, the DropDownList will be pre-selected with the previous item the user has chosen.
The ddlProductMake_SelectedIndexChanged event is where we actually do the cascading feature by populating the second DropDownList based from the value selected from the first DropDownList. But before that we need to cast the sender of the object triggering the event to determine which DropDownList is triggered within the GridView row. We then cast the NamingContainer of the sender to a type of GridViewRow to determine the actual row the user is editing.

The ddlProductModel_SelectedIndexChanged event is where we update the Price value based on the selected Model from the second DropDownList. It basically uses LINQ syntax to query the DataSource and to get the Price based on the selected value of the ddlModel.

Binding the GridView

Finally let's call the BindGrid() method to populate the GridView. Here's the code block below.
  1. protected void Page_Load(object sender, EventArgs e)  
  2.  {  
  3.             if (!IsPostBack)  
  4.             {  
  5.                 BindGrid();  
  6.             }  
  7.         }  
The Output

Here are some screenshots from running this code in the page:



That's it! I hope someone finds this article useful.


Similar Articles