0

I am looking for a way to have the "Already" selected and saved value show in the edit form. If user selects to change then it will populate a dropdown. There is a similar question on here like this, however it is not MVC and unfortunately it was not answered and the person who posted it went a different direction all together. I am pretty sure that this can be achieved through javascript/ajax. I am not a javascript/ajax programmer so I am stuck at what to change. The cascading dropdown populates correctly and works fine. Other than the fact that you actually have to select another country first and then change back to original company for the state list to populate. The current JavaScript/ajax populates a dropdown and then stores the "Country, State, City" Ids in the record being saved. This makes it confusing also because I do not want to see the Id in the stored value but the actual name. My code is below.

EDIT This was marked a duplicate, However it really isn't. The Key factor in this Question is "Cascading Dropdown Lists" in which 1 dropdown relies on the one before it to get the information it needs. In the beginning I did not have an issue with this. Now I do! I have implemented what was done in this post Html.DropdownListFor selected value not being set. I now can see the selected items as selected in the dropdowns. That's great, but it broke my cascading effect. It now just populates the whole list in each dropdown. When I try to save it, it will not, I now get an error on save -

System.InvalidOperationException: 'The ViewData item that has the key 'Country' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.'

So the solutions that are given really only touch on part of the problem I am having. New code is below and I also had to use 'public IEnumerable States { get; set; }' in my view model the proposed 'public SelectList States {get; set;}' did not work as it was looking for an IEnumerable.

EDIT - Changed the 3 Dropdowns. Here are the 3 dropdowns in razor page:

                            <div class="form-group row">
                            @Html.LabelFor(model => model.Country, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                @Html.DropDownListFor(model => Model.Country, Model.Countries, "Select Country", new { @class = "form-control", @id = "ddlCountry" })
                                @Html.ValidationMessageFor(model => model.Country, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group row">
                            @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                @Html.DropDownListFor(model => model.State, Model.States, "Select State", new { @class = "form-control", @id = "ddlState" })
                                @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group row">
                            @Html.LabelFor(model => model.City, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">

                                @Html.DropDownListFor(model => model.City, Model.Cities, "Select City" , new { @class = "form-control", @id = "ddlCity" })
                                @Html.ValidationMessageFor(model => model.City, "", new { @class = "text-danger" })
                            </div>
                        </div>

This is the JavaScript/Ajax: NOTE: I have tried changing the -

"<option value="' + value.StateId + '">"

To be:

"<option value="' + value.Name + '">"

But does not work as the code relies on the "id" to fetch the next dropdown.

<script type="text/javascript">

$(function () {
    $('#ddlCountry').change(function () {
        $.ajax({
            type: "post",
            url: "/Customer/GetStates",
            data: { countryId: $('#ddlCountry').val() },
            datatype: "json",
            traditional: true,
            // *can you add the following error callback in your code and update what comes in the console log?*
            error: function(_, err) {
                console.log(_, err)
            },
            success: function (data) {
                $.each(data, function (index, value) {
                    $('#ddlState').append('<option value="' + value.StateId + '">' + value.StateName + '</option>');
                });
            }
        });
    });

    $('#ddlState').change(function () {
        $.ajax({
            type: "post",
            url: "/Customer/GetCities",
            data: { stateId: $('#ddlState').val() },
            datatype: "json",
            traditional: true,
            error: function(_, err) {
                console.log(_, err)
            },
            success: function (data) {
                $.each(data, function (index, value) {
                    $('#ddlCity').append('<option value="' + value.CityId + '">' + value.CityName + '</option>');
                });
            }
        });
    });
});

EDIT - New Controller Action. Here is my Controller Actions:

        public ActionResult UserEditAddress(Guid guid)
    {

        if (guid == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Addresses addresses = db.Addresses.Find(guid);
        if (addresses == null)
        {
            return HttpNotFound();
        }
        var model = new EditAddressViewModel();
        model.AddressUI = addresses.AddressUI;
        model.Line1 = addresses.Line1;
        model.Line2 = addresses.Line2;
        model.Country = addresses.Country;
        model.State = addresses.State;
        model.City = addresses.City;
        model.ZipCode = addresses.ZipCode;
        model.CompanyId = addresses.CompanyId;
        model.Countries = new SelectList(db.Countries, "CountryId", "CountryName", addresses.Country);
        model.States = new SelectList(db.States, "StateId", "StateName", addresses.State);
        model.Cities = new SelectList(db.Cities, "CityId", "CityName", addresses.City);

        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult UserEditAddress(EditAddressViewModel model)
    {
        var userId = User.Identity.GetUserId();
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        Addresses addresses = db.Addresses.Find(model.AddressUI);
        addresses.Line1 = model.Line1;
        addresses.Line2 = model.Line2;
        addresses.Country = model.Country;
        addresses.State = model.State;
        addresses.City = model.City;
        addresses.ZipCode = model.ZipCode;
        addresses.CompanyId = model.CompanyId;

        db.Entry(addresses).Property(x => x.AddressId).IsModified = false;
        db.Entry(addresses).Property(x => x.IsBilling).IsModified = false;
        db.Entry(addresses).Property(x => x.IsShipping).IsModified = false;
        db.Entry(addresses).Property(x => x.ContactName).IsModified = false;
        db.Entry(addresses).Property(x => x.EmailAddress).IsModified = false;
        db.Entry(addresses).Property(x => x.PhoneNumber).IsModified = false;
        db.Entry(addresses).Property(x => x.FaxNumber).IsModified = false;
        //db.Entry(addresses).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index", "Customer", new { UserId = userId });

    }

    // Get States
    public JsonResult GetStates(string countryId)
    {
        int Id;
        Id = Convert.ToInt32(countryId);

        var states = from a in db.States where a.CountryId == Id select a;
        return Json(states);

    }

    // Get Cities
    public JsonResult GetCities(string stateId)
    {
        int Id;
        Id = Convert.ToInt32(stateId);

        var cities = from a in db.Cities where a.StateId == Id select a;
        return Json(cities);
    }

Your help would be much appreciated as I have spent entirely to long trying to find a resolution for this. Thank you for your help, in advance.

S.Purtan
  • 125
  • 1
  • 2
  • 11
  • Possible duplicate (https://stackoverflow.com/questions/19476530/html-dropdownlistfor-selected-value-not-being-set) – Ryan Wilson Dec 10 '18 at 15:57
  • Possible duplicate of [Html.DropdownListFor selected value not being set](https://stackoverflow.com/questions/19476530/html-dropdownlistfor-selected-value-not-being-set) – Tetsuya Yamamoto Dec 11 '18 at 01:36
  • So before I go and say that the above link answered my question, I implemented this in my solution. I cannot comment on the post because you have to have a Rep of 50. When I run it I am getting System.InvalidOperationException: 'The ViewData item that has the key 'Country' is of type 'System.Int32' but must be of type 'IEnumerable'.' So I am not sure why or where this issue is taking place. – S.Purtan Dec 11 '18 at 05:56

2 Answers2

0

This situation i have face before. and i solved it in this way! by using viewModel. the above example Type is the domain model that have several values in the database. in the edit mode i simply initialize the viewModel in the action Method and pass it to the view.

   public ActionResult Edit(int id)
            {
                Customer customer = _context.Customers.SingleOrDefault(x => x.Id == id);
                if (customer == null)
                    return HttpNotFound();

                CustomerViewModel viewModel = new CustomerViewModel()
                {
                    Customer = customer,
                    Type = _context.Type
                };

                return View("Customer_Form", viewModel);
            }

the alternative view for the dropdownlist will be.

@Html.DropDownListFor(m => m.Customer.Type , new SelectList(Model.Type , "Id", "Name"), "Select Type", new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.Customer.Type )

Type Class.

public class Type
{
    public int Id { get; set; }
    public string typeName { get; set; }
}

Type ViewModel.

public class CustomerViewModel
{
    public IEnumerable<Type> Types { get; set; }
    public Customer Customer { get; set; }
}

I hope my answer will be helpful! for you.

Rehan Shah
  • 1,505
  • 12
  • 28
  • So I am already using a viewModel. Could you please share what the ViewModel Type is? When I put "Type" in my code is says that the viewModel doesn't have a definition for type. Thanks – S.Purtan Dec 10 '18 at 16:23
  • i added Type, CustomerViewModel Code to my answer...! – Rehan Shah Dec 10 '18 at 16:44
  • Thanks, the code you represent is only returning a single dropdown and getting an Id. Min is getting a GUID and returning 3 dropdowns in which I would need to get an ID for each dropdown in the string callback so that I could create separate contexts for each. Because the Id for Countries is not the same as the GUID for the record being edited. Unless I am just not understanding what you are doing. – S.Purtan Dec 10 '18 at 17:02
  • Okay! Thank you! if i am not wrong ? please marked it as answer by clicking the check sign on the left! – Rehan Shah Dec 10 '18 at 17:09
0

Through much research and I am still not completely sure that this is the proper way to do this, but it works. So if you are in need of a cascading dropdown with edit this is the way I have done it.

I will start with the view model.

    public class EditAddressViewModel
    {
    public Guid AddressUI { get; set; }

    [Display(Name = "Billing Address?")]
    [UIHint("_IsStatus")]
    public bool IsBilling { get; set; }

    [Display(Name = "Shipping Address?")]
    [UIHint("_IsStatus")]
    public bool IsShipping { get; set; }

    [Display(Name = "Location Name")]
    [Required(ErrorMessage = "Location name is required")]
    public string LocationName { get; set; }

    [Display(Name = "Contact Name")]
    [Required(ErrorMessage = "Contact name is required")]
    public string ContactName { get; set; }

    [Display(Name = "Address")]
    public string Line1 { get; set; }

    [Display(Name = "Address 2")]
    [Required(ErrorMessage = "Address line 2 is required - Should be your address or PO Box")]
    public string Line2 { get; set; }

    [Display(Name = "Country")]
    [Required(ErrorMessage = "Country name is required")]
    public int Country { get; set; }
    public IEnumerable<SelectListItem> Countries { get; set; }

    [Display(Name = "State")]
    [Required(ErrorMessage = "State name is required")]
    public int State { get; set; }
    public IEnumerable<SelectListItem> States { get; set; }

    [Display(Name = "City")]
    [Required(ErrorMessage = "City name is required")]
    public int City { get; set; }
    public IEnumerable<SelectListItem> Cities { get; set; }

    [Display(Name = "ZipCode")]
    public string ZipCode { get; set; }

    [Display(Name = "Contact Email")]
    [Required(ErrorMessage = "Email contact is required")]
    [DataType(DataType.EmailAddress)]
    [StringLength(320)]
    [RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", ErrorMessage = "Enter a valid email address")]
    public string EmailAddress { get; set; }

    [Display(Name = "Phone Number")]
    [Required(ErrorMessage = "Phone number is required")]
    [DataType(DataType.PhoneNumber)]
    [StringLength(12)]
    [RegularExpression(@"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}", ErrorMessage = "Enter a valid phone number")]
    public string PhoneNumber { get; set; }

    [Display(Name = "Fax Number")]
    [DataType(DataType.PhoneNumber)]
    [StringLength(12)]
    [RegularExpression(@"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}", ErrorMessage = "Enter a valid phone number")]
    public string FaxNumber { get; set; }

    public int CompanyId { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd HH:mm}", ApplyFormatInEditMode = false)]
    public DateTime CreatedDate { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd HH:mm}", ApplyFormatInEditMode = false)]
    public DateTime LastUpdated { get; set; }

}

The next thing is the Controller.

    // Customer Input
    // GET: Addresses/Edit?guid=56A792FE-28D1-4D1D-8F59-21DE1EABA2FB
    // TO DO - Create Route in APP_Start RouteConfig for better looking link.
    [Authorize(Roles = "CompanyAdmin")]
    public ActionResult UserEditAddress(Guid guid)
    {
        if (guid == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        AddressEdit addresses = db.AddressEdit.Find(guid);
        if (addresses == null)
        {
            return HttpNotFound();
        }
        var model = new EditAddressViewModel();
        model.AddressUI = addresses.AddressUI;
        model.Line1 = addresses.Line1;
        model.Line2 = addresses.Line2;
        model.Country = addresses.Country;
        model.State = addresses.State;
        model.City = addresses.City;
        model.ZipCode = addresses.ZipCode;
        model.Countries = new SelectList(db.Countries, "CountryId", "CountryName", addresses.Country);
        model.States = new SelectList(db.States, "StateId", "StateName", addresses.State);
        model.Cities = new SelectList(db.Cities, "CityId", "CityName", addresses.City);

        return View(model);
    }

    // POST: Addresses/Edit?guid=56A792FE-28D1-4D1D-8F59-21DE1EABA2FB
    // TO DO - Create Route in APP_Start RouteConfig for better looking link.
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see https://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult UserEditAddress([Bind(Include = "AddressUI,Line1,Line2,Country,State,City,ZipCode,CompanyId")] AddressEdit addresses)
    {
        var userId = User.Identity.GetUserId();
        if (ModelState.IsValid)
        { 
            db.Entry(addresses).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index", "Customer", new { UserId = userId });
        }

        return View(addresses);
    }

    // Get States
    public JsonResult GetStates(string countryId)
    {
        int Id;
        Id = Convert.ToInt32(countryId);

        var states = from a in db.States where a.CountryId == Id select a;
        return Json(states);

    }


    // Get Cities
    public JsonResult GetCities(string stateId)
    {
        int Id;
        Id = Convert.ToInt32(stateId);

        var cities = from a in db.Cities where a.StateId == Id select a;
        return Json(cities);
    }

Notice above I am using a Guid. This was put in place so that a customer cannot see the "ID" of the record and change it in the link to view other customer data. I also Implemented a SQL View for 2 reasons. The id is being used for other procedures and in entity framework it must be a Key. So I created the SQL View so I can use the model attached to it and put the Guid as the Key. Also used this for the purpose of only needing to update some of the information in the table on edit and not all fields so I only have the fields in the model that need updated.

The SQL View - Model

namespace MyProject.BusinessModels.Entities
{
    public class AddressEdit
    {
    [Key]
    public Guid AddressUI { get; set; }
    [Display(Name = "Address")]
    public string Line1 { get; set; }
    [Display(Name = "Address 2")]
    public string Line2 { get; set; }
    [Display(Name = "Country")]
    public int Country { get; set; }
    [Display(Name = "State")]
    public int State { get; set; }
    [Display(Name = "City")]
    public int City { get; set; }
    [Display(Name = "ZipCode")]
    public string ZipCode { get; set; }
    }
}

And here is the Razor Page with the Dropdown Lists for Country, State, and City. This setup brings the edit page up with the already selected values in the database. They are Integers. The dropdowns display the String values from the 3 separate tables holding the data names and corresponding Id's. The javascript/ajax functions will not work by just dropping the lists because the current state has already retrieved an entire list of each of the 3 tables. So I had to add a clear button wrapping the entire script to give it focus after the clear. So this sets the country ddl to 0 and clears the list for the State and City. Now the cascading effect with the script works and you can change values to update. If the user never clears them and only changes a different field on the form, it will keep the values that are "Selected". If they do clear the form and try to submit they will get an error as the fields are required.

@model MyProject.Models.EditAddressViewModel
@using Microsoft.AspNet.Identity
@{
ViewBag.Title = "UserEdit";
Layout = "~/Views/Shared/CustomerDashboardLayout.cshtml";
}

<br />

<div class="row">
    <div class="col-lg-12">
        <p>
            @{ var userId = User.Identity.GetUserId(); }

            @Html.ActionLink("Back to List", "Index", "Customer", new { UserId = userId }, new { @class = "btn btn-primary" })
        </p>
    </div>
    <!-- /.col-lg-12 -->
</div>
<div class="row">
    <div class="col-lg-6">
        <div class="tile">
            <h3 class="tile-title">Edit Address</h3>
            <div class="tile-body">
                @using (Html.BeginForm())
                {
                    @Html.AntiForgeryToken()

                <div class="form-horizontal">

                    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

                    @Html.HiddenFor(model => model.AddressUI)

                    <div class="form-group row">
                        @Html.LabelFor(model => model.Line1, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">
                            @Html.EditorFor(model => model.Line1, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Line1, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group row">
                        @Html.LabelFor(model => model.Line2, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">
                            @Html.EditorFor(model => model.Line2, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Line2, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group row">
                        @Html.LabelFor(model => model.Country, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">
                            @Html.DropDownListFor(model => Model.Country, Model.Countries, "Select Country", new { @class = "form-control", @id = "ddlCountry" })
                            @Html.ValidationMessageFor(model => model.Country, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group row">
                        @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">
                            @Html.DropDownListFor(model => model.State, Model.States, "Select State", new { @class = "form-control", @id = "ddlState" })
                            @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group row">
                        @Html.LabelFor(model => model.City, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">

                            @Html.DropDownListFor(model => model.City, Model.Cities, "Select City", new { @class = "form-control", @id = "ddlCity" })
                            @Html.ValidationMessageFor(model => model.City, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group row">
                        @Html.LabelFor(model => model.ZipCode, htmlAttributes: new { @class = "control-label col-md-3" })
                        <div class="col-md-8">
                            @Html.EditorFor(model => model.ZipCode, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.ZipCode, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="tile-footer">
                        <div class="row">
                            <div class="col-md-8 col-md-offset-3">
                                <input type="button" id="btnReset" value="Reset" onclick="Reset();" class="btn btn-default" />
                                <input type="submit" value="Save" class="btn btn-primary" />
                            </div>
                        </div>
                    </div>
                </div>
                }

            </div>
        </div>
    </div>
</div>
<script type="text/javascript">
    function Reset() {
        document.getElementById('ddlCountry').selectedIndex = 0;
        document.getElementById('ddlState').innerHTML = "";
        document.getElementById('ddlCity').innerHTML = "";


        $(function () {
            $('#ddlCountry').change(function () {
                $.ajax({
                    type: "post",
                    url: "/Addresses/GetStates",
                    data: { countryId: $('#ddlCountry').val() },
                    datatype: "json",
                    traditional: true,
                    error: function (_, err) {
                        console.log(_, err)
                    },
                    success: function (data) {
                        $.each(data, function (index, value) {
                            $('#ddlState').append('<option value="' + value.StateId + '">' + value.StateName + '</option>');
                        });
                    }
                });
            });

            $('#ddlState').change(function () {
                $.ajax({
                    type: "post",
                    url: "/Addresses/GetCities",
                    data: { stateId: $('#ddlState').val() },
                    datatype: "json",
                    traditional: true,
                    error: function (_, err) {
                        console.log(_, err)
                    },
                    success: function (data) {
                        $.each(data, function (index, value) {
                            $('#ddlCity').append('<option value="' + value.CityId + '">' + value.CityName + '</option>');
                        });
                    }
                });
            });
        });
    }

</script>

This is a working solution. However in a perfect world there would not be a clear button. So in the future I will be working to find a way to get rid of that. Hope this helps someone in need of a easy way to edit and view current values of Cascading Drop down lists

S.Purtan
  • 125
  • 1
  • 2
  • 11