Ajax Control Toolkit
Using CascadingDropDown with a Database

The sample that ships with the Toolkit uses the CascadingDropDown with an XML data store. A common question is how to use it with a database. This walkthrough will demonstrate that.

First, create a new ASP.NET AJAX Website, and add a reference to the toolkit assembly. You'll find the assembly (called AjaxControlToolkit.dll) in the "AjaxControlToolkit\bin" directory where you installed the toolkit.

In the default.aspx page and add some dropdowns to it:

<div>
Make: <asp:DropDownList ID="ddlMake"
runat="server"/><br/>

Model: <asp:DropDownList ID="ddlModel"
runat="server"/><br/>

Color: <asp:DropDownList ID="ddlColor"
runat="server"/>

<br />
<asp:Button ID="Button1"
runat="server" Text="Submit" />

</div>
Now, at the top of your ASPX page, register a prefix for the reference to the toolkit:
<%@ Register
Assembly="AjaxControlToolkit"
Namespace
="AjaxControlToolkit"
TagPrefix="ajaxToolkit" %>
And then add the extender itself:
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown1"
runat="server"/>
At runtime, the extender will make callbacks to a web service we specify (which is decorated with the System.Web.Script.Services.ScriptService attribute). In that web service, it expects a WebMethod with the following signature (note that parameter names must match too!):
[WebMethod]
public CascadingDropDownNameValue[] GetColorsForModel(
string knownCategoryValues,
string category)
The knownCategoryValues parameter will return a string containing the currently selected category values, as well as the category to retrieve values for. For example, if the extender is populating the "Color" field, you will be passed the values for the "Make" and "Model" fields, as well as "Color" to specify the field to return values for.

The CascadingDropDown class has a helper method for unpacking the category values: StringDictionary kv = CascadingDropDown
.ParseKnownCategoryValuesString(knownCategoryValues);

This method will return a StringDictionary containing the name/value pairs of the currently selected values. So imagine you've got a database with tables for the Make (manufacturer), Model, and Color information, and you're accessing that database through a DataSet to which you've added methods for getting each set of values.

The web method to get the available colors for a given model would look like this:

[WebMethod]
public CascadingDropDownNameValue[] GetColorsForModel(
string knownCategoryValues,
string category)
{

StringDictionary kv =
CascadingDropDown.ParseKnownCategoryValuesString(
knownCategoryValues);


int modelId;
if (!kv.ContainsKey("Model") ||
!Int32.TryParse(kv["Model"], out modelId))

{
return null;
}

CarsTableAdapters.ColorTableAdapter adapter =
new
CarsTableAdapters.ColorTableAdapter();

Cars.ColorDataTable colorTable =
adapter.GetColorsForModel(modelId);


List<CascadingDropDownNameValue> values =
new
List<CascadingDropDownNameValue>();

foreach (DataRow dr in colorTable)
{

values.Add(new CascadingDropDownNameValue(
(string) dr["Color"],
dr["ColorID"].ToString()));
}

return values.ToArray();

}
So it's simple to return the values. Note that the values are returned as an array of CascadaingDropDownNameValue structures. This structure also includes an isDefaultValue boolean field that allows the specification of which value should be selected when the list is first displayed.

Now let's hook up our extender:
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown1"
runat="server"

TargetControlID="ddlMake"
Category="Make"
PromptText="Select a manufacturer"
ServicePath="CarsService.asmx"
ServiceMethod="GetCarMakes" />
If you look at this it's pretty simple. TargetControlID specifies which control we're extending, in this case it's the drop down that specifies the manufacturer or "make" of the car. PromptText specifies the text to show in the dropdown when no value is selected, and the ServicePath and ServiceMethod attributes tell the extender which web service to call to fetch its values.

We can also do this hook up from the designer. If you switch to design view, and select the "ddlModel" DropDownList, you can make these hookups in the property browser at design time:



Note the "ParentControlID" property which specifies which DropDownList is the "parent" for this one. By setting these parent values, you can chain or "cascade" these values, and the CascadingDropDown extender will automatically manage setting, clearing, and loading the data for you.

If you set up the ddlModel and ddlColor lists as well and go back to source view, you'll see:
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown1"
runat="server"

TargetControlID="ddlMake"
Category="Make"
PromptText="Select a manufacturer"
ServicePath="CarsService.asmx"
ServiceMethod="GetCarMakes" />
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown2"
runat="server"

TargetControlID="ddlModel"
ParentControlID="ddlMake"
PromptText="Please select a model"
ServiceMethod="GetModelsForMake"
ServicePath="CarsService.asmx"
Category="Model" />
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown3"
runat="server"

TargetControlID="ddlColor"
ParentControlID="ddlModel"
PromptText="Please select a color"
ServiceMethod="GetColorsForModel"
ServicePath="CarsService.asmx"
Category="Color" />
Once you've completed your web service methods, your cascading drop down is complete!

Finally, in order for the values to be submitted, EventValidation needs to be disabled for the page. EventValidation ensures that the values in each control match the values that were present when the page was rendered, but since these drop downs are populating on the client side, this is never true. We’re hoping to find a way to resolve this issue but please ensure that you understand the potential risks of this and validate the data appropriately in your post back when using this control.

Full WebService Code
using System;
    using System.Web;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using AjaxControlToolkit;
    using System.Data;
    using System.Data.SqlClient;

    /// <summary>
    /// Summary description for CarData
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class CarData : System.Web.Services.WebService
    {
      public CarData()
      {
        //Uncomment the following line if using designed components 
        //InitializeComponent(); 
      }

      [WebMethod]
      public CascadingDropDownNameValue[] GetMakes(
        string knownCategoryValues,
        string category)
      {
        CarsTableAdapters.MakeTableAdapter makeAdapter =
          new CarsTableAdapters.MakeTableAdapter();
        Cars.MakeDataTable makes = makeAdapter.GetMakes();
        List<CascadingDropDownNameValue> values =
          new List<CascadingDropDownNameValue>();
        foreach (DataRow dr in makes)
        {
          string make = (string)dr["Make"];
          int makeId = (int)dr["MakeID"];
          values.Add(new CascadingDropDownNameValue(
            make, makeId.ToString()));
        }
        return values.ToArray();
      }

      [WebMethod]
      public CascadingDropDownNameValue[] GetModelsForMake(
        string knownCategoryValues,
        string category)
      {
        StringDictionary kv =
CascadingDropDown.ParseKnownCategoryValuesString( knownCategoryValues); int makeId; if (!kv.ContainsKey("Make") || !Int32.TryParse(kv["Make"], out makeId)) { return null; } CarsTableAdapters.ModelTableAdapter makeAdapter = new CarsTableAdapters.ModelTableAdapter(); Cars.ModelDataTable models = makeAdapter.GetModelsForMake(makeId); List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>(); foreach (DataRow dr in models) { values.Add(new CascadingDropDownNameValue( (string)dr["Model"], dr["ModelID"].ToString())); } return values.ToArray(); } [WebMethod] public CascadingDropDownNameValue[] GetColorsForModel( string knownCategoryValues, string category) { StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString( knownCategoryValues); int modelId; if (!kv.ContainsKey("Model") || !Int32.TryParse(kv["Model"], out modelId)) { return null; } CarsTableAdapters.ColorTableAdapter adapter = new CarsTableAdapters.ColorTableAdapter(); Cars.ColorDataTable colorTable = adapter.GetColorsForModel(modelId); List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>(); foreach (DataRow dr in colorTable) { values.Add(new CascadingDropDownNameValue( (string)dr["Color"], dr["ColorID"].ToString())); } return values.ToArray(); } }



Copyright © 2006-2009 Microsoft Corporation. All Rights Reserved.