6

I'm having trouble finding a tutorial / video that shows how to implement Cascading DropDownList from a Database using EntityFramework. I'm using ASP.NET MVC Core, EntityFramework Core with C#.

As of now, I'm able to retrieve the data from my database to my 3 DropDownList fine.

What I would like to be able to accomplish is to have the user select a State first which would then display all Cities related to that State. Then after user has selected a City it would display the Zip Code(s) related to the City.

Any help would be greatly appreciated.

Models

    public class Customer
{
    public int CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public int StateId { get; set; }
    public int CityId { get; set; }
    public int ZipId { get; set; }

    public State State { get; set; }
    public City City { get; set; }
    public Zip Zip { get; set; }
}

    public class State
{
    public int StateId { get; set; }
    public string Abbr { get; set; }

    public List<Customer> Customers { get; set; }
}

    public class City
{
    public int CityId { get; set; }
    public string Name { get; set; }

    public int StateId { get; set; }
    public State State { get; set; }

    public List<Customer> Customers { get; set; }
}

    public class Zip
{
    public int ZipId { get; set; }
    public string PostalCode { get; set; }

    public int CityId { get; set; }
    public City City { get; set; }

    public List<Customer> Customers { get; set; }
}

ViewModels

    public class CustomerFormVM
{
    public int CustomerId { get; set; }

    [Display(Name = "First Name")]
    [StringLength(50)]
    public string FirstName { get; set; }

    [Display(Name = "Last Name")]
    [StringLength(50)]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Select State")]
    [Display(Name = "State")]
    public int StateId { get; set; }

    //public IEnumerable<State> States { get; set; }
    public IEnumerable<SelectListItem> States { get; set; }

    [Required(ErrorMessage = "Select City")]
    [Display(Name = "City")]
    public int CityId { get; set; }

    //public IEnumerable<City> Citys { get; set; }
    public IEnumerable<SelectListItem> Citys { get; set; }

    [Required(ErrorMessage = "Select Zip")]
    [Display(Name = "Zip")]
    public int ZipId { get; set; }

    //public IEnumerable<Zip> Zips { get; set; }
    public IEnumerable<SelectListItem> Zips { get; set; }
}

CustomerController

public class CustomerController : Controller
{
    private MultiDbContext db;

    public CustomerController(MultiDbContext context)
    {
        db = context;
    }

    // GET: /<controller>/
    public IActionResult Index()
    {
        return View(db.Customers.ToList());
    }

    public IActionResult getCititesFromDatabaseByStateId(int id)
    {
        return View(db.Citys.Where(c => c.StateId == id).ToList());
    }

    public IActionResult getCities(int id)
    {
        var cities = new List<City>();
        cities = getCititesFromDatabaseByStateId(id); //call repository
        return Json(cities);
    }

    public ActionResult Create()
    {
        var states = db.States.ToList();
        var citys = db.Citys.ToList();
        var zips = db.Zips.ToList();

        var viewModel = new CustomerFormVM
        {
            States = states,
            Citys = citys,
            Zips = zips
        };

        return View(viewModel);
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(CustomerFormVM vm)
    {
        if (ModelState.IsValid)
        {
            var customer = new Customer();
            {
                customer.FirstName = vm.FirstName;
                customer.LastName = vm.LastName;
                customer.StateId = vm.StateId;
                customer.CityId = vm.CityId;
                customer.ZipId = vm.ZipId;
            }
            db.Customers.Add(customer);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        else
        {
            vm.States = db.States.ToList();
            vm.Citys = db.Citys.ToList();
            vm.Zips = db.Zips.ToList();
            return View(vm);
        }
    }


    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var customervm = new CustomerFormVM();
        {
            Customer customer = db.Customers.SingleOrDefault(c => c.CustomerId == id);

            if (customer == null)
            {
                return NotFound();
            }

            customervm.CustomerId = customer.CustomerId;
            customervm.FirstName = customer.FirstName;
            customervm.LastName = customer.LastName;

            // Retrieve list of States
            var states = db.States.ToList();
            customervm.States = states;

            // Retrieve list of Citys
            var citys = db.Citys.ToList();
            customervm.Citys = citys;

            // Retrieve list of Citys
            var zips = db.Zips.ToList();
            customervm.Zips = zips;

            // Set the selected state
            customervm.StateId = customer.StateId;

            // Set the selected city
            customervm.CityId = customer.CityId;

            // Set the selected zip
            customervm.ZipId = customer.ZipId;
        }
        return View(customervm);
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(CustomerFormVM vmEdit)
    {
        if (ModelState.IsValid)
        {
            Customer customer = db.Customers.SingleOrDefault(c => c.CustomerId == vmEdit.CustomerId);

            if (customer == null)
            {
                return NotFound();
            }

            customer.FirstName = vmEdit.FirstName;
            customer.LastName = vmEdit.LastName;
            customer.StateId = vmEdit.StateId;
            customer.CityId = vmEdit.CityId;
            customer.ZipId = vmEdit.ZipId;

            db.Entry(customer).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(vmEdit);
    }
}

Create View

        <div class="form-group">
        @Html.LabelFor(c => c.FirstName)
        @Html.TextBoxFor(c => c.FirstName, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(c => c.LastName)
        @Html.TextBoxFor(c => c.LastName, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @*@Html.LabelFor(s => s.StateId)
            @Html.DropDownListFor(s => s.StateId, new SelectList(Model.States, "StateId", "Abbr"), "", new { @class = "form-control" })
            @Html.ValidationMessageFor(s => s.StateId)*@

        <label asp-for="StateId "></label>
        <select asp-for="StateId " asp-items="Model.States" class="form-control" id="state-target"></select>
        <span asp-validation-for="StateId " class="text-danger"></span>
    </div>

    <div class="form-group">
        @*@Html.LabelFor(ct => ct.CityId)
            @Html.DropDownListFor(ct => ct.CityId, new SelectList(Model.Citys, "CityId", "Name"), "", new { @class = "form-control" })
            @Html.ValidationMessageFor(ct => ct.CityId)*@

        <label asp-for="CityId"></label>
        <select asp-for="CityId" asp-items="Model.Citys" class="form-control" id="city-target"></select>
        <span asp-validation-for="CityId" class="text-danger"></span>
    </div>

    <div class="form-group">
        @Html.LabelFor(z => z.ZipId)
        @Html.DropDownListFor(z => z.ZipId, new SelectList(Model.Zips, "ZipId", "PostalCode"), "", new { @class = "form-control" })
        @Html.ValidationMessageFor(z => z.ZipId)
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
}

@section scripts {
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    <script src="~/lib/js/example.js"></script>,
}
Brian Brian
  • 159
  • 2
  • 3
  • 13
  • You need to add some js to handle change event on your drop down lists – H. Herzl Dec 07 '16 at 16:42
  • @H. Herzl, Is there a way to handle that without JS? – Brian Brian Dec 07 '16 at 16:45
  • According to my knowledge you need to use JS to solve this requirement – H. Herzl Dec 07 '16 at 18:55
  • the only JS you need is in the form of an inline attribute, i.e. `onchange="this.form.submit()"`. You need to handle that post in the controller and populate new collections for the dropdowns. – Mackan Dec 09 '16 at 16:06
  • You can try using JQuery instead of JS https://stackoverflow.com/questions/11273544/how-to-fill-cascading-dropdownlist-each-other-by-using-jquery-in-mvc-3 – Jason Ebersey Jan 02 '18 at 21:14

1 Answers1

10

I had a similar situation but in my example I have a Root folder and depending on which root folder I am using the next drop down list would display the corresponding sub-folders.

Not sure if there is a purly asp.net solution but, I used Jquery/Ajax for this.

Your code should look something like this:

html list:

<label asp-for="StateId "></label>
<select asp-for="StateId " asp-items="Model.States" class="form-control" id="state-target"></select>
<span asp-validation-for="StateId " class="text-danger"></span>

<label asp-for="CityId"></label>
<select asp-for="CityId" asp-items="Model.Citys" class="form-control" id="city-target"></select>
<span asp-validation-for="CityId" class="text-danger"></span>

Jquery code, you write this in .js file and then add it to a specific view with this statement<script src="~/js/example.js"></script>, Don't forget you need to add a jquery library to your project before any other javascript, and your example.js will contain:

$(document).ready(function () {
  $("#state-target").on("change", function () {
    $list = $("#city-target");
    $.ajax({
        url: "/getCities",
        type: "GET",
        data: { id: $("#state-target").val() }, //id of the state which is used to extract cities
        traditional: true,
        success: function (result) {
            $list.empty();
            $.each(result, function (i, item) {
                $list.append('<option value="' + item["CityId"] + '"> ' + item["Name"] + ' </option>');
            });
        },
        error: function () {
            alert("Something went wrong call the police");
        }
    });
  });
});

The Ajax request will call this action in the Controller which will retrieve a list of cities from the database (using something like return dbContext.CityTable.Where(c => c.StateId == id).ToList() inside a getCititesFromDatabaseByStateId(id) method) and then return the Json object, the success function will create a list of options and apply it:

public IActionResult getCities(int id)
{
    var cities = new List<City>();
    cities = getCititesFromDatabaseByStateId(id); //call repository
    return Json(citites);
}

In your ViewModel consider changing IEnumerable<State/City/Zip> (IEnumerable<T>) to IEnumerable<SelectListItem>. I can say as well your Model's are messy (but if you can get data the from the database focus on getting the list working 1st), consider improving them later.

Fix for 2 errors mentioned in the comments:

public List<City> getCititesFromDatabaseByStateId(int id)
{
    return db.Citys.Where(c => c.StateId == id).ToList();
}

public ActionResult Create()
{
     var states = new SelectList(db.States.ToList(), "StateId", "Abbr");
     var citys = new SelectList(db.Citys.ToList(), "CityId", "Name");
     var zips = new SelectList(db.Zips.ToList(), "ZipId", "Code");

     var viewModel = new CustomerFormVM
     {
         States = states,
         Citys = citys,
         Zips = zips
     };

     return View(viewModel);
}
Anton Toshik
  • 2,621
  • 2
  • 19
  • 42
  • @anton-toshnik I'm sorry, I'm not familiar with JS/Jquery at the moment. Is there anyway you can assist me with what you supplied based on the code I posted? I tried to add the code for the **Contoller** that you supplied but I get a red line on `return Json(List);` The message on that line states **'List' is a type, which is not valid in the given context** – Brian Brian Dec 08 '16 at 18:20
  • Okey I see where your coming from, Ill make a chat room where I can explain everything to you. – Anton Toshik Dec 09 '16 at 09:36
  • Alright, it seems I can't make a chat room, Ill add more detail to the answer, and i'd recommend going through some jquery tutorials. – Anton Toshik Dec 09 '16 at 09:58
  • Ok, I made the changes that you suggested and I'm going to include those updates to my original post. I'm getting red error messages though on my CustomerController now after I added the code you suggested. – Brian Brian Dec 09 '16 at 19:16
  • The red errors that I'm getting on my CustomerController are on `public IActionResult getCities(int id)` **Cannot implicitly convert type Microsoft.AspNetCore.Mvc.IActionResult to System.Collections.Generic.LIst. An explicity conversion exists (are you missing a cast?)** – Brian Brian Dec 09 '16 at 19:26
  • Then on the `public ActionResult Create()` the 3 errors are on states, citys an zips. That error states **Cannot implicitly convert type System.Collections.Generic.List to System.Collections.Generic.IEumerable. An explicit conversion exists. Are you missing a cast?** – Brian Brian Dec 09 '16 at 19:31
  • I apologize for all the comments. It would be easier if we were able to chat. – Brian Brian Dec 09 '16 at 19:31
  • @BrianBrian 1st Error make sure you `return Json(x)`, `x` being a `List` type object AND check that you are NOT trying to assign a IActionResult to a List object at the //call repository stage . 2nd error you need to convert `List` to `SelectListItem` you can look up how to do this. – Anton Toshik Dec 12 '16 at 09:40
  • Ok, I will look to see how to do what you mentioned. I'm still learning so It may take a little time :) – Brian Brian Dec 12 '16 at 17:02
  • Still not having any luck on figuring out what you last posted. I've searched for tutorials on this and they all seem to look different. I'll keep trying though. – Brian Brian Dec 14 '16 at 20:30
  • @BrianBrian I have added the code you should change – Anton Toshik Dec 15 '16 at 16:35
  • Thank you, I tried what you posted but when I run the page it populates the first dropdown with the first state and city in my database. Then, if I choose another State it populates the correct number of cities but all the city names are called "undefined". – Brian Brian Dec 16 '16 at 20:11
  • @BrianBrian you will need to debug this yourself. Use break points and see where the data is missing or the potential mistake is. Then google that, if you would like give me your skype/email I can get in contact with you personally. and attempt to help you with the code. – Anton Toshik Dec 19 '16 at 10:07
  • thank you! I would truly appreciate it! You could email me at info@ocait.com – Brian Brian Jan 06 '17 at 00:14