Click here to Skip to main content
Click here to Skip to main content

Cascading Menus Without Page Refresh With the AJAX Control Toolkit

By , 12 Jul 2011
 

Sample Image

Introduction

This is a very easy and user friendly technique for web applications to implement cascading menus without page refresh.

You do not have to know about AJAX functions, just download the AJAX Control Toolkit on CodePlex and follow the steps described in this article. When you have two dropdownlists and both of them are related to each other, such as Country and City in a registration page where you want to save information about the user's hometown. When you choose a certain row from the first dropdownlist (Country), you will expect the second dropdownlist (City) to be filtered according to the chosen row without refreshing the page. This article shows a very easy and user friendly solution that can be used in web applications.

Background: What is the AJAX Control Toolkit?

The ASP.NET AJAX Control Toolkit is an Open-Source project built on top of the Microsoft ASP.NET AJAX framework, and contains more than 30 controls that enable you to easily create rich, interactive web pages. If you want to know more about it, visit here.

Using the code

The first step is to download the AJAX Control Toolkit from here for .NET 3.5 or here for .NET 4.0.

Copy the AJAX Control Toolkit to the Bin folder and right click on Solution, choose Add Reference, in the Browse tab, double click on the Bin folder, and double click on AJAX Control Toolkit, then on the Build menu, click Rebuild.

Database

Create a database and name it "Db". Here is the query to create the required tables:

--Create Country Table 
CREATE TABLE [dbo].[tblCountry](
    [Country] [nvarchar](50) NULL,
    [IDC] [int] IDENTITY(1,1) NOT NULL
    ) ON [PRIMARY]

--Fill Country Table 
insert into dbo.tblCountry(Country) values('United States')
insert into dbo.tblCountry(Country) values('United Kingdom')
insert into dbo.tblCountry(Country) values('Spain')
insert into dbo.tblCountry(Country) values('France')
insert into dbo.tblCountry(Country) values('Norway')

--Create City Table 
CREATE TABLE [dbo].[tblCity](
[City] [nvarchar](50) NULL,
[CountryID] [int] Not NULL,
[ID] [int] IDENTITY(1,1) NOT NULL
)
ON [PRIMARY]

--Fill City Table 
insert into dbo.tblCity(City,CountryID) values('Michigan',1)
insert into dbo.tblCity(City,CountryID) values('New York',1)
insert into dbo.tblCity(City,CountryID) values('London',2)
insert into dbo.tblCity(City,CountryID) values('Barcelona',3)
insert into dbo.tblCity(City,CountryID) values('Madrid',3)
insert into dbo.tblCity(City,CountryID) values('Paris',4)
insert into dbo.tblCity(City,CountryID) values('Kristiansand',5) 
insert into dbo.tblCity(City,CountryID) values('Oslo',5)

Visual Studio 2008 - .NET 3.5

Create a website and name it Cascading menus. Create a Web Form and name it CascadingDropDown.aspx. In the HTML view, write the code below. The code is a little different between C# and VB in this section. If you are a VB coder, modify two sections in the page tag in the first line:

  • language="VB"
  • CodeFile="CascadingDropDown.aspx.vb"
<%@ Page Language="C#" AutoEventWireup="false" 
         CodeFile="CascadingDropDown.aspx.cs" Inherits="CascadingDropDown" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
         TagPrefix="ajaxToolkit" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>CascadingDropDown</title>
</head>
<body>
<form id="form1" runat="server">
<ajaxToolkit:ToolkitScriptManager  ID="ScriptManager1" runat="server" > 
</ajaxToolkit:ToolkitScriptManager>
<div>
<asp:DropDownList ID="ddListCountry" runat="server" Width="170" /> 

<asp:DropDownList ID="ddListCity" runat="server" Width="170" /> 

<ajaxToolkit:CascadingDropDown 
  ID="CascadingCountry" 
  runat="server"
  TargetControlID="ddListCountry"
  Category="Country"
  PromptText="Please select a Country"
  LoadingText="[Loading Country...]"
  ServicePath="Cascading.asmx"
  ServiceMethod="GetCountries" >
  </ajaxToolkit:CascadingDropDown>

<ajaxToolkit:CascadingDropDown 
  ID="CascadingCity" 
  runat="server"
  TargetControlID="ddListCity"
  Category="City"
  PromptText="Please select a City"
  LoadingText="[Loading City...]"
  ServicePath="Cascading.asmx"
  ServiceMethod="GetCities"
    ParentControlID="ddListCountry" >
  </ajaxToolkit:CascadingDropDown>
</div>

</form>
</body>
</html>

For VB: Create Web Service: Solution > right click > Add New Item > Web Service >. Name: Cascading.asmx. Language: Visual Basic. Go To > App_Code > Cascading.vb.

' (c) Copyright Microsoft Corporation.
' This source is subject to the Microsoft Public License.
' See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
' All other rights reserved.
Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Collections.Generic
Imports AjaxControlToolkit
Imports System.Data
Imports System.Data.SqlClient
Imports System.Text.RegularExpressions
Imports System.Collections.Specialized
Imports System.Xml

' To allow this Web Service to be called from script,
' using ASP.NET AJAX, uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class Cascading
    Inherits System.Web.Services.WebService
    Dim cn As New SqlClient.SqlConnection()
    Dim ds As New DataSet
    Dim dt As New DataTable

<WebMethod()> _
Public Function GetCountries(ByVal knownCategoryValues As String, _
       ByVal category As String) As CascadingDropDownNameValue()

    'ADO.Net
    Dim strCn As String = "data source=.;Initial Catalog=Db;Integrated Security=True"

    cn.ConnectionString = strCn
    Dim cmd As New SqlClient.SqlCommand
    cmd.Connection = cn
    cmd.CommandType = CommandType.Text
    cmd.CommandText = "select * from tblCountry"

    Try
        cn.Open()
        cmd.ExecuteNonQuery()
        Dim da As New SqlDataAdapter(cmd)
        da.Fill(ds)
    Catch ex As Exception
    Finally
        cn.Close()
    End Try

    dt = ds.Tables(0)
    Dim CountryValues As New List(Of CascadingDropDownNameValue)()

    For Each row As DataRow In dt.Rows
       CountryValues.Add(New CascadingDropDownNameValue(row("Country").ToString(), _
                         row("IDC").ToString()))
    Next

    Return CountryValues.ToArray()

End Function

<WebMethod()> _
Public Function GetCities(ByVal knownCategoryValues As String, _
                ByVal category As String) As CascadingDropDownNameValue()

    Dim kv As StringDictionary = _
        CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues)
    'ContainsKey("Country") is one of property in Ajaxcontroltoolkit
    Dim countryId As Integer

    If ((Not kv.ContainsKey("Country")) Or _
        (Not Int32.TryParse(kv("Country"), countryId))) Then
        Return Nothing
    End If

    'ADO.Net
    Dim strCn As String = "data source=.;Initial Catalog=Db;Integrated Security=True"
    cn.ConnectionString = strCn
    Dim cmd As New SqlClient.SqlCommand
    cmd.Connection = cn
    '-----I Defined a parameter instead of passing value 
    '               directly to prevent sql injection--------'
    cmd.CommandText = "select * from tblCity where CountryID=@myParameter Order by City"
    cmd.Parameters.AddWithValue("@myParameter", countryId.ToString())

    Try
        cn.Open()
        cmd.ExecuteNonQuery()
        Dim da As New SqlDataAdapter(cmd)
        da.Fill(ds)
    Catch ex As Exception
    Finally
        cn.Close()
    End Try

    dt = ds.Tables(0)
    Dim CityValues As New List(Of CascadingDropDownNameValue)()

    For Each row As DataRow In dt.Rows
       CityValues.Add(New CascadingDropDownNameValue(row("City").ToString(), _
                      row("ID").ToString()))
    Next

    Return CityValues.ToArray()

End Function

End Class

For C#: Web Service: Solution > right click > Add New Item > Web Service >. Name: Cascading.asmx. Language: C#. Go To > App_Code > Cascading.cs.

// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License.
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
// All other rights reserved.
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using AjaxControlToolkit;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Xml;

///<summary>
/// Summary description for Cascading
///</summary>

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script,
// using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class Cascading : System.Web.Services.WebService {

    public Cascading () {
        //Uncomment the following line if using designed components
        //InitializeComponent();
    }

    [WebMethod]
    public CascadingDropDownNameValue[] GetCountries(string knownCategoryValues, 
                                        string category)
    {
        //ADO.Net
        SqlConnection cn =new SqlConnection();
        DataSet ds = new DataSet();
        DataTable    dt = new DataTable();
        string strCn = "data source=.;Initial Catalog=Db;Integrated Security=True";
        cn.ConnectionString = strCn;
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = cn;
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = "select * from tblCountry";

        try
        {
            cn.Open();
            cmd.ExecuteNonQuery();
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            da.Fill(ds);
        }
        catch
        {
        }
        finally
        {
            cn.Close();
        }
        dt = ds.Tables[0];

        List<CascadingDropDownNameValue> CountryValues = 
                    new List<CascadingDropDownNameValue>();
        foreach (DataRow row   in dt.Rows)
        {
            CountryValues.Add(new CascadingDropDownNameValue(
              row["Country"].ToString(), row["IDC"].ToString()));
        }

        return CountryValues.ToArray();
    }

    [WebMethod]
    public CascadingDropDownNameValue[] GetCities(string knownCategoryValues, 
                                                  string category)
    {
        StringDictionary kv = 
          CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);

        //'ContainsKey("Country") is one of property in Ajaxcontroltoolkit
        int countryId;

        countryId = System.Convert.ToInt32(kv["Country"]);

        //ADO.Net
        SqlConnection cn = new SqlConnection();
        DataSet ds = new DataSet();
        DataTable    dt = new DataTable();
        string strCn = 
          "data source=.;Initial Catalog=Db;Integrated Security=True";

        cn.ConnectionString = strCn;
        SqlCommand cmd = new SqlCommand();

        cmd.Connection = cn;
        cmd.CommandType = CommandType.Text;
        //-----I Defined a parameter instead of passing value 
        //     directly to prevent sql injection--------//
        cmd.CommandText = "select * from tblCity where CountryID=@myParameter Order by City";
        cmd.Parameters.AddWithValue("@myParameter", countryId.ToString());
        try
        {
            cn.Open();
            cmd.ExecuteNonQuery();
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            da.Fill(ds);
        }
        catch
        {
        }
        finally
        {
            cn.Close();
        }
        dt = ds.Tables[0];

        List<CascadingDropDownNameValue> CityValues = 
                  new List<CascadingDropDownNameValue>();
        foreach (DataRow row   in dt.Rows)
        {
            CityValues.Add(new CascadingDropDownNameValue(
               row["City"].ToString(), row["ID"].ToString()));
        }
        return CityValues.ToArray();
    }
}

GetCountries and GetCities are two functions that get two string arguments: knownCategoryValues and category. They have an output which is an array of strings: CascadingDropDownNameValue. In the GetCountries function, the Country dropdownlist is filled: we connect to the database and execute a query using ADO.NET, then in the foreach loop, fill the dropdownlist. When you select a certain row (in the above example, a country such as United States), knownCategoryValues will be equal to "country:1", which means category is country and knownCategoryValues is "1". 1 is the ID for United States in the database. In the GetCities function, the City dropdownlist is filled and filtered by knownCategoryValues, which is "1" in this case. In the Web Form, I have specified ParentControlID="ddListCountry" in CascadingCity, so the parent is country and the city will be filtered by the country ID. Additionally, I defined a parameter instead of passing value directly, to prevent SQL Injection.

Trying the demo step by step

  1. Go here for .NET 3.5 or here for .NET 4.0 and download the AJAX Control Toolkit file.
  2. Copy the folder "AjaxControlToolkit.Dll" and all its dependencies, they are 18 objects, to your web site in the Bin folder (C:\Cascading\Bin).
  3. Right click on Solution, choose Refresh, then right click again and click Add reference. Then in the Browse tab, double click on the Bin folder and double click on ajaxcontroltoolkit. On the Build menu > click Rebuild.
  4. Create the database and tables like above, and add some rows which have common words.
  5. Create a Web Form and name it CascadingDropDown.aspx. In the HTML view, write some code like above. (This should be exactly like my code because this section is case sensitive.)
  6. Create a Web Service: Solution > right click > Add New Item > Web Service > Name: Cascading.asmx. Language: C# or VB. Go to > App_Code > Cascading.cs or Cascading.vb.
  7. If you are a VB coder, use the VB sample, otherwise use the C# sample.
  8. Run the program, select a country such as United States, and you will see a list of cities in that country such as Detroit or New York City.

Feedback

Feel free to leave any feedback on this article; it is a pleasure to see your opinions and vote about this code. If you have any questions, please do not hesitate to ask me here.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Mahsa Hassankashi
Software Developer
Iran (Islamic Republic Of) Iran (Islamic Republic Of)
Member
I have been working with .Net framework for 7 years.
I`d like to challenge with complex problem, then make it easy for using everyone. This is the best joy.
 
-------------------------------------------------------------
Diamond is nothing except the pieces of the coal which have continued their activities finally they have become Diamond.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDynamicPopulateExtender,memberMr. Truong Pham27 Mar '13 - 0:34 
Cascading DropDown is a great thing. With AjaxToolkit, the heart of post-loading technique is DynamicPopulateExtender which enables dynamic loading on demand.
 
Thank you for great article.
QuestionDownloadmemberT Sully21 Apr '12 - 3:57 
Hi,
 
Both downloads are zip files of three .jpg files.
GeneralSamething in .net 4.0 and MVC3memberquentininsa19 Jan '12 - 3:09 
I needed to do the same thing in .Net4.0 using MVC3 and JQuery made it possible.
 
Created a seperate script like below
$(function () {
    /* -- Cascading dropdowns -----------------
    data-action = "testing"
    data-child  = "childcontrol"
    ---------------------------------------- */
    $(":input[data-child]").change(function () {
        var currVal = $(this).val();
        var _action = $(this).attr("data-action");
        var _child = $(this).attr("data-child");
 
        var childSelect = $('#' + _child);
        childSelect.empty();
        childSelect.append($('<option/>', { value: "--All--", text: "--All--" }));
 
        if (currVal != null && currVal != '') {
            $.getJSON(_action, { selectedVal: currVal }, function (ChildData) {
                
                $.each(ChildData, function (index, sourceVal) {
                    childSelect.append($('<option/>', {
                        value: sourceVal.value,
                        text: sourceVal.text
                    }));
                });
            });
        }
    });
})
 
Then, in my view, I use:
 
...
 
<div class="editor-field">
  @Html.DropDownListFor(model => model.StaffBranchID, new SelectList ViewBag.Branches, "BranchID", "BranchName"), "-- Select --", new { data_action="getDivisions", data_child="StaffDivisionID" })
            @Html.ValidationMessageFor(model => model.StaffBranchID)
        </div>
...
 
data_action="getDivisions" is the Action on the controller
data_child="StaffDivisionID" is the control to replace the result with.
 
My action looks like this:
[HttpGet]
public ActionResult getDivisionStaff(Guid selectedVal)
{
  return Json(db.StaffListByDivision(selectedVal).Select(x => new { value=x.StaffID, text=x.StaffFullName}), JsonRequestBehavior.AllowGet);
}
I have several Dropdownlists on the same View, linked up this way and they all work like a charm! Smile | :)
GeneralRe: Samething in .net 4.0 and MVC3memberMahsa Hassankashi22 Jan '12 - 13:46 
Thanks for nice idea
Excellent Thumbs Up | :thumbsup:
Questionnice jobmembershaheen_mix31 Dec '11 - 0:53 
realy nice Smile | :)
AnswerRe: nice jobmemberMahsa Hassankashi31 Dec '11 - 20:32 
It is nice of you Smile | :)
BugSQL InjectionmemberMihai Maerean11 Jul '11 - 18:43 
This line of code is subject to textbook SQL injection:
cmd.CommandText ="select * from tblCity where CountryID=" + countryId.ToString() + "Order by City";
 
Please mention in a code comment that this line MUST be changed (e.g. to a parametrized query) in websites that go live. Sooner of later, every website has visitors that try basic SQL injections on it (for fun or profit).
 
And it's not funny at all when it happens.
Mihai

GeneralRe: SQL InjectionmemberMahsa Hassankashi11 Jul '11 - 22:44 
Hello Dear Mihai,
It is true in My file, I wrote it true in html too!!!
By the way I will make it true ASAP.
Thanks for advice.Thumbs Up | :thumbsup:
GeneralMy vote of 5memberArlen Navasartian11 Jul '11 - 6:39 
Nice
GeneralRe: My vote of 5memberMahsa Hassankashi11 Jul '11 - 12:33 
Dear Arlen, Thank you Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 12 Jul 2011
Article Copyright 2011 by Mahsa Hassankashi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid