Cascading DropDown in MVC4 Using Knockout, Web API With Entity Framework

In this article I will explore many things, including dropdown cascading, how to use Entity Framework and how to use a data transfer object with Entity Framework and how to use the ASP.NET Web API and fetch data using the ASP.NET Web API and display using Knockout.

Web API

The ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. The ASP.NET Web API is an ideal platform for building REST applications on the .NET Framework.

Knockout

Knockout is a JavaScript library that helps you to create rich, responsive displays and editor user interfaces with a clean underlying data model. Read more here: knockoutjs.

Entity Framework

Entity Framework (EF) is an object-relational mapper that enables .NET developers to work with relational data using domain-specific objects. It eliminates the need for most of the data-access code that developers usually need to write.

Data Transfer Objects

Data Transfer Objects are reusable classes that contain related data and no business logic. They may be defined on the service side, client side, or both sides of the communication channel. Their properties (in other words getters/setters) may wrap primitive data types (for example integers, strings, and so on) or other DTOs.

Getting Started

  • Create a new Project. Open Visual Studio 2012.
  • Go to "File" -> "New" -> "Project...".
  • Select "Web" in the installed templates.
  • Select "ASP.NET MVC 4 Web Application".
  • Enter the Name and choose the location.
  • Click "OK".

This is my database schema that is used in this sample:

database schema
As you all know when we use the Entity Framework it builds default model classes and context classes, so we will use a data transfer object besides using Entity Framework models and context classes.

So first of all add data transfer object model classes:

public class CountryDTO

{

    public int CountryId { get; set; }

    public string CountryName { get; set; }       

}

public class StateDTO

{

    public int StateId { get; set; }

    public int CountryId { get; set; }

    public string StateName { get; set; }  

}

public class CityDTO

{

     public int StateId { get; set; }

     public int CityId { get; set; }

     public string CityName { get; set; }

}

Now add a converter class as in the following:

public static class Converter

{

        public static CountryDTO CountriesToCountryDTO(Countries e)

        {

            return new CountryDTO

            {

                CountryId = e.CountryId,

                CountryName = e.CountryName

            };

        }

 

        public static List<CountryDTO> LCountriesToCountryDTO(List<Countries> e)       

        {

            List<CountryDTO> lstCountryDTO = e.Select(

              country => new CountryDTO()

              {

                  CountryId = country.CountryId,

                  CountryName = country.CountryName

              }).ToList();

            return lstCountryDTO;

        }

 

        public static StateDTO StatesToStateDTO(States e)

        {

            return new StateDTO

            {

                StateId = e.StateId,

                StateName = e.StateName

            };

        }      

 

        public static List<StateDTO> LStatesToStateDTO(List<States> e)

        {

            List<StateDTO> lstStateDTO = e.Select(

             state => new StateDTO()

             {

                 StateId = state.StateId,

                 StateName = state.StateName

             }).ToList();

            return lstStateDTO;

        }

 

        public static CityDTO CitiesToCityDTO(Cities e)

        {

            return new CityDTO

            {

                CityId = e.CityId,

                CityName = e.CityName

            };

        }

 

        public static List<CityDTO> LCitiesToCityDTO(List<Cities> e)

        {

            List<CityDTO> lstCityDTO = e.Select(

             city => new CityDTO()

             {

                 CityId = city.CityId,

                 CityName = city.CityName

             }).ToList();

            return lstCityDTO;

        }

  }

Here is my repository class:

public class LocationRepository : ILocationRepository

{

        public List<CountryDTO> GetCountries()

        {

            using (TestDBEntities dbcontext1 = new TestDBEntities())

            {

                var lstCountries = from r in dbcontext1.Countries select r;

                List<CountryDTO> lst = new List<CountryDTO>();

                lst = Converter.LCountriesToCountryDTO(lstCountries.ToList());

                return lst;

            }

        }

 

        public List<StateDTO> GetStates(int countryId)

        {

            using (TestDBEntities dbcontext = new TestDBEntities())

            {

                var lstStates = dbcontext.States.Where(b => b.CountryId == countryId).ToList();

                List<StateDTO> list = new List<StateDTO>();

                list = Converter.LStatesToStateDTO(lstStates.ToList());

                return list;

            }

        }

 

        public List<CityDTO> GetCities(int stateId)

        {

            using (TestDBEntities dbcontext = new TestDBEntities())

            {               

                var lstCities = dbcontext.Cities.Where(b => b.StateId == stateId).ToList();

                List<CityDTO> list = new List<CityDTO>();

                list = Converter.LCitiesToCityDTO(lstCities.ToList());

                return list;

            }

        }

  }

Repository interface

public interface ILocationRepository

{

       List<CountryDTO> GetCountries();

       List<StateDTO> GetStates(int countryId);

       List<CityDTO> GetCities(int stateId);

}

Now add a Web API controller class:



This is my controller class code:

public class LocationController : ApiController

{

        static readonly ILocationRepository repository = new LocationRepository(); 

        // GET api/<controller>        

        public IEnumerable<CountryDTO> GetCountries()

        {

            //return new string[] { "value1", "value2" };

            return repository.GetCountries();

        }

 

        // GET api/<controller>/5 

        [ActionName("GetStates")]

        public IEnumerable<StateDTO> GetStates(int id)

        {

            return repository.GetStates(id);

        }

 

        // GET api/<controller>/5    

        [ActionName("GetCities")]

        public IEnumerable<CityDTO> GetCities(int id)

        {

            return repository.GetCities(id);

        } 

        // POST api/<controller>

        public void Post([FromBody]string value)

        {

        } 

        // PUT api/<controller>/5

        public void Put(int id, [FromBody]string value)

        {

        } 

        // DELETE api/<controller>/5

        public void Delete(int id)

        {

        }

    }

To stop method conflics we have added an ActionName to two methods.

Now add this routing to the WebApiConfig class:

public static void Register(HttpConfiguration config)

{

    config.Routes.MapHttpRoute(

    name: "DefaultApi",

    routeTemplate: "api/{controller}/{id}",

    defaults: new { id = RouteParameter.Optional }

  );

    config.Routes.MapHttpRoute(

     name: "DefaultApiWithAction",

     routeTemplate: "api/{controller}/{action}/{id}",

     defaults: new { id = RouteParameter.Optional }

   );

}

Here we are done with the model and controller parts, now time to work on the view part.

@{

    ViewBag.Title = "Cascading DropDown using Knockout / WebAPI Sample";

}

<script src="~/Scripts/jquery-1.8.2.min.js"></script>

<script src="~/Scripts/knockout-2.2.0.js"></script>

 <script type="text/javascript">       

        $(document).ready(function () {

            FetchCountries();

            $("#Country").change(function () {

                var countryId = $("#Country").val();                           

                $.ajax({

                    type: "GET",

                    url: "http://localhost:62830/api/Location/GetStates/" + countryId,

                    contentType: "application/json; charset=utf-8",

                    dataType: "json",

                    success: function (response) {

                        if (response != "") {

                            $(response).each(function (index, element) {

                                viewModel.stateCollection.push(element);

                            });

                            ko.applyBindings(viewModel);

                        }

                    }

                });

            });

 

            $("#State").change(function () {

                var stateId = $("#State").val();               

                $.ajax({

                    type: "GET",

                    url: "http://localhost:62830/api/Location/GetCities/" + stateId,

                    contentType: "application/json; charset=utf-8",

                    dataType: "json",

                    success: function (response) {

                        if (response != "") {

                            $(response).each(function (index, element) {

                                viewModel.cityCollection.push(element);

                            });

                            ko.applyBindings(viewModel);

                        }

                    }

                });

            });

        });

 

        function FetchCountries() {           

            viewModel = {

                countryCollection: ko.observableArray(),

                stateCollection: ko.observableArray(),

                cityCollection: ko.observableArray()

            };                    

            $.ajax({

                type: "GET",

                url: "http://localhost:62830/api/Location",

                contentType: "application/json; charset=utf-8",

                dataType: "json",

                success: function (response) {

                    if (response != "") {                       

                        $(response).each(function (index, element) {

                            viewModel.countryCollection.push(element);

                        });

                        ko.applyBindings(viewModel);

                    }

                }

            });

        }

    </script>

 

<h2>Cascading DropDown using Knockout / WebAPI Sample</h2>

 

 Country Name: <select data-bind="options: countryCollection, optionsCaption: 'Choose country...',

    optionsValue: function (item) { return item.CountryId; },

    optionsText: function (item) { return item.CountryName; }, value: Country,

    valueUpdate: 'change'" id="Country" name="Country"></select>

<br />

 

State Name: <select data-bind="options: stateCollection, optionsCaption: 'Choose state...',

    optionsValue: function (item) { return item.StateId; },

    optionsText: function (item) { return item.StateName; },  value: State,

    valueUpdate: 'change'" id="State" name="State"></select>

<br />   

 

City Name: <select data-bind="options: cityCollection, optionsCaption: 'Choose city...',

    optionsValue: function (item) { return item.CityId; },

    optionsText: function (item) { return item.CityName; }, value: City,

    valueUpdate: 'change'" id="City" name="City"></select>

All set. Now run the application as in the following:
run the application

 
Select State
 
Select City