Cascading Dropdown List With MVC, LINQ to SQL and AJAX

This article explains how to populate a child DropDownList depending on each new value as values are selected in the parent's DropDown List.

This article explains how to populate a child dropdown list depending on each new value as values are selected in the parent's DropDown List.

Database Design

I use two tables to populate DropDown Lists. One is a Country table and another is State table. The Country table is a parent table and the State table is a child table. These tables have primary key and foreign key relationships using the CountryId column.

MVC-1.jpg

Relationship between Country table and State table:

  1. CREATE TABLE Country  
  2. (  
  3.      Id int Primary Key Identity(1,1),  
  4.      Name NVarchar(50) Not Null  
  5. )  
  6.    
  7. CREATE TABLE [State]  
  8. (  
  9.      Id int Primary Key Identity(1,1),  
  10.      CountryId int Not Null,  
  11.      Name NVarchar(50) Not Null  
  12. )
Create a relationship between Country table and State table using foreign key. It is a one to many relationship where a single country has multiple states.
  1. ALTER TABLE [State]  
  2. ADD CONSTRAINT FK_COUNTRY_STATE FOREIGN KEY(CountryId) REFERENCES  
  3. Country(Id) ON DELETE CASCADE
First populate the parent dropdown list using the Country table then populate the child dropdown list using the State table by the selecting the CountryId from the parent dropdown list. 

Create an MVC Application

I will create a MVC application using Visual Studio 2012. So let's see the procedure for creating a MVC application.

Step 1: Go to "File" -> "New" -> "Project...".

Step 2: Choose "ASP.NET MVC 4 Web Application" from the list then give the application name "CountryStateApplication" and set the path in the location input where you want to create the application.

Step 3: Now choose the Project Template "Empty" and select "Razor" as the view engine from the dropdown list.

Adding a LINQ to SQL Class

Entity classes are created and stored in LINQ to SQL Classes files (.dbml files). The O/R Designer opens when you open a .dbml file. It is a  DataContext class that contains methods and properties for connecting to a database and manipulating the data in the database.  The DataContext name corresponds to the name that you provided for the .dbml file

Step 1: Right-click on the Models folder in the Solution Explorer then go to "Add" and click on "Class.."

Step 2: Choose "LINQ to SQL Classes" from the list and give the name "Address" for the dbml name.  After that click on "Add".

MVC-2.jpg

Figure 1.2: Create dbml file for Database operation

Step 3: Drag both tables (Country table and State table) from the database in the Server Explorer and drop onto the O/R Designer surface of the "Address.dbml" file.

MVC-3.jpg

Figure 1.3: Tables in Address.dbml file

Using the Repository Pattern

The Repository Pattern, according to Martin Fowler, is a repository that mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to the repository for satisfaction. Objects can be added to and removed from the repository, since they can form a simple collection of objects, and the mapping code encapsulated by the repository will carry out the appropriate operations behind the scenes. Conceptually, a repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. The repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers. In practice, it is usually a collection of data access services, grouped in a similar way to the domain model classes.

I implement the Repository pattern by defining one repository class for both domain model entities that require specialized data access methods. A repository class contains the specialized data access methods required for its corresponding domain model entity.

MVC-4.jpg

Figure 1.4: Address repository interface and class

When you create the repository class, you create an interface that represents all of the methods used by the repository class. Within your controllers, you write your code against the interface instead of the repository. That way, you can implement the repository using various data access technologies in the future. So first of all you need to create an interface "IAddressRepository" under the Models folder that contains basic access methods for country and states by countryId.

  1. using System.Collections.Generic;  
  2.   
  3. namespace CountryStateApplication.Models  
  4. {  
  5.      public interface IAddressRepository  
  6.     {  
  7.          IList<Country> GetAllCountries();  
  8.          IList<State> GetAllStatesByCountryId(int countryId);  
  9.     }  
  10. }
Thereafter create a repository class "AdderessRepository" that implements the "IAddressRepository" interface under the Models folder.
  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3.   
  4.  namespace CountryStateApplication.Models  
  5. {  
  6.      public class AddressRepository : IAddressRepository  
  7.     {  
  8.          private AddressDataContext _dataContext;  
  9.    
  10.          public AddressRepository()  
  11.         {  
  12.             _dataContext = new AddressDataContext();  
  13.         }  
  14.    
  15.          public IList<Country> GetAllCountries()  
  16.         {            
  17.              var query = from countries in _dataContext.Countries  
  18.                          select countries;  
  19.              var content = query.ToList<Country>();  
  20.              return content;             
  21.         }  
  22.          public IList<State> GetAllStatesByCountryId(int countryId)  
  23.         {  
  24.              var query = from states in _dataContext.States  
  25.                          where states.CountryId == countryId  
  26.                          select states;  
  27.              var content = query.ToList<State>();  
  28.              return content;  
  29.         }  
  30.     }  
  31. }
Create Model Class

The MVC Model contains all application logic (business logic, validation logic, and data access logic), except pure view and controller logic. Create a "AddressModel" class under the Models folder and create properties for label field and dropdown list values.

  1. using System.Collections.Generic;  
  2. using System.ComponentModel.DataAnnotations;  
  3. using System.Web.Mvc;   
  4.   
  5. namespace CountryStateApplication.Models  
  6. {  
  7.      public class AddressModel  
  8.     {  
  9.          public AddressModel()  
  10.         {  
  11.             AvailableCountries = new List<SelectListItem>();  
  12.             AvailableStates = new List<SelectListItem>();  
  13.         }  
  14.         [Display(Name="Country")]  
  15.          public int CountryId { getset; }  
  16.          public IList<SelectListItem> AvailableCountries { getset; }  
  17.         [Display(Name = "State")]  
  18.          public int StateId { getset; }  
  19.          public IList<SelectListItem> AvailableStates { getset; }  
  20.     }  
  21. }
Create Controller
 
You need to create a controller to handle request from the browser. In this application I created the "AddressController" controller under the Controllers folder with two action methods. One action method "Index" is used to display the view with countries data filled in in the country dropdown list by browser request and another action method "GetStatesByCountryId" is used to fill in the state dropdown list based on the selected country from the country dropdown list.
  1. using System;  
  2. using System.Linq;  
  3. using System.Web.Mvc;  
  4. using CountryStateApplication.Models;  
  5.   
  6. namespace CountryStateApplication.Controllers  
  7. {  
  8.     public class AddressController : Controller  
  9.     {  
  10.          private IAddressRepository _repository;  
  11.    
  12.          public AddressController() : this(new AddressRepository())  
  13.         {  
  14.         }  
  15.    
  16.          public AddressController(IAddressRepository repository)  
  17.         {  
  18.             _repository = repository;  
  19.         }  
  20.        public ActionResult Index()  
  21.        {  
  22.              AddressModel model = new AddressModel();  
  23.             model.AvailableCountries.Add(new SelectListItem { Text = "-Please select-", Value = "Selects items" });  
  24.              var countries = _repository.GetAllCountries();  
  25.              foreach (var country in countries)  
  26.             {  
  27.                 model.AvailableCountries.Add(new SelectListItem()  
  28.                 {  
  29.                     Text = country.Name,  
  30.                     Value = country.Id.ToString()  
  31.                 });  
  32.             }  
  33.              return View(model);  
  34.         }  
  35.    
  36.         [AcceptVerbs(HttpVerbs.Get)]  
  37.          public ActionResult GetStatesByCountryId(string countryId)  
  38.         {              
  39.              if (String.IsNullOrEmpty(countryId))  
  40.             {  
  41.                  throw new ArgumentNullException("countryId");  
  42.             }  
  43.              int id = 0;  
  44.              bool isValid = Int32.TryParse(countryId, out id);            
  45.             var states = _repository.GetAllStatesByCountryId(id);  
  46.              var result = (from s in states  
  47.                           select new  
  48.                          {  
  49.                              id = s.Id,  
  50.                              name = s.Name  
  51.                          }).ToList();            
  52.              return Json(result, JsonRequestBehavior.AllowGet);  
  53.         }  
  54.    
  55.     }  
  56. }
Create Route 

You need to create a route to call action method of controller by ajax so add new route "GetStatesByCountryId" in RouteConfig class (RouteConfig.cs file) under App_Start folder.

  1. using System.Web.Mvc;  
  2. using System.Web.Routing;  
  3. namespace CountryStateApplication  
  4. {  
  5.     public class RouteConfig  
  6.     {  
  7.         public static void RegisterRoutes(RouteCollection routes)  
  8.         {  
  9.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");   
  10.             routes.MapRoute(  
  11.                 name: "Default",  
  12.                 url: "{controller}/{action}/{id}",  
  13.                 defaults: new { controller = "Address", action = "Index", id = UrlParameter.Optional }  
  14.             );  
  15.             routes.MapRoute("GetStatesByCountryId",  
  16.                             "address/getstatesbycountryid/",  
  17.                             new { controller = "Address", action = "GetStatesByCountryId" },  
  18.                             new[] { "CountryStateApplication.Controllers" });  
  19.         }  
  20.     }  
  21. }
Create View

A view is used to display data in a browser. I created the "Index.cshtml" view to display data under the Address folder of the View folder. You need to add a reference for jQuery so you can use the ajax method of jQuery in the view.

  1. <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script> 
You need to call the change event of the country dropdown list method when a new country item is selected from the dropdown list. When an item selected from the dropdown then an ajax call is made, on success of the call of the action method the state dropdown list is filled with data.
  1. @model CountryStateApplication.Models.AddressModel  
  2. @{  
  3.     ViewBag.Title = "Index";  
  4. }  
  5.   
  6. <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>  
  7. <script type="text/javascript">  
  8.     $(function () {  
  9.         $("#CountryId").change(function () {  
  10.                  var selectedItem = $(this).val();  
  11.                  var ddlStates = $("#StateId");  
  12.               var statesProgress = $("#states-loading-progress");  
  13.              statesProgress.show();  
  14.              $.ajax({  
  15.                  cache: false,  
  16.                  type: "GET",  
  17.                  url: "@(Url.RouteUrl("GetStatesByCountryId"))",  
  18.                     data: { "countryId": selectedItem },  
  19.                     success: function (data) {                         
  20.                         ddlStates.html('');  
  21.                         $.each(data, function (id, option) {  
  22.                             ddlStates.append($('<option></option>').val(option.id).html(option.name));  
  23.                         });  
  24.                         statesProgress.hide();  
  25.                     },  
  26.                     error: function (xhr, ajaxOptions, thrownError) {  
  27.                         alert('Failed to retrieve states.');  
  28.                         statesProgress.hide();  
  29.                     }  
  30.                 });  
  31.             });  
  32.         });  
  33.      </script>   
  34. <div>  
  35.      @Html.LabelFor(model=>model.CountryId)  
  36.      @Html.DropDownListFor(model=>model.CountryId, Model.AvailableCountries)  
  37. </div>  
  38. <br />  
  39. <div>  
  40.      @Html.LabelFor(model => model.StateId)  
  41.      @Html.DropDownListFor(model => model.StateId, Model.AvailableStates)  
  42.      <span id="states-loading-progress" style="display: none;">Please wait..</span>  
  43. </div>
MVC-5.jpg 

Output screen of application.