neda Derakhshesh neda Derakhshesh - 2 months ago 11
Ajax Question

how to save items of cascading drop down list in database with asp.net mvc

My Drop-Downs works well the problem is when I want to save my form.

here is my controller

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,TourId,StartDate,EndDate")] TourDate tourDate)
{
if (ModelState.IsValid)
{
db.TourDates.Add(tourDate);
db.SaveChanges();

return RedirectToAction("Index", "Home");
}

ViewBag.TourId = new SelectList(db.Tours, "Id", "TourName", tourDate.TourId);
return RedirectToAction("Index", "test");
}


[HttpPost]
public JsonResult GetT(int? id)
{
var tours = db.Tours.Where(e => e.CountryId == id).ToList()
.Select(e => new
{
Id = e.Id,
TourName= e.TourName
}).ToList();


return Json(tours);
}


and here is My form. in this From,
select
tag with
id=TourId
is filling with data in ajax from the outer drop down
dropdownId
and it works fine

@Html.DropDownList("dropdownId",
Model.Countries.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.CountryName
}),
new { @class = "form-control" })


@using (Html.BeginForm("Create", "test"))
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
<h4>TourDate</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })

<div class="form-group">
@Html.LabelFor(model => model.TourDate.TourId, "TourId", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<select class="form-control" id="TourId"></select>

@Html.ValidationMessageFor(model => model.TourDate.TourId, "", new { @class = "text-danger" })
</div>
</div>

<br />
<div class="form-group">
@Html.LabelFor(model => model.TourDate.StartDate, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.TourDate.StartDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.TourDate.StartDate, "", new { @class = "text-danger" })
</div>
</div>
<br />
<div class="form-group">
@Html.LabelFor(model => model.TourDate.EndDate, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.TourDate.EndDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.TourDate.EndDate, "", new { @class = "text-danger" })
</div>
</div>

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>


}

the problem is when I submit the form no diffrent which Tour is chosen always there is TourId=0.

enter image description here

Appreciate any help and also here is the ajax if needed

$("#dropdownId").change(function () {
$('#TourId').empty();
var countrySelected = $(this).val();

$.ajax
({
url: '/test/GetT/' + countrySelected,
type: 'POST',
data: {
'countryId': countrySelected
},
success: function (data)
{
var $select = $('#TourId');
for (var i = 0; i < data.length; i++)
{

$('<option/>').attr('value', data[i].Id).html(data[i].TourName).appendTo('#TourId');

}



}
});

});

Answer

The reason your second <select> element does not submit a value is because it does not have a name attribute. It would need to be

<select class="form-control" name="TourDate.TourId" id="TourId"></select>

However there are multiple other errors and problems with your code. To note just a couple of them:

  1. The model in your view is is not typeof TourDate (its a model containing a property which is TourDate) so none of your controls can be bound to your TourDate tourDate parameter in the POST method (the model in the parameter needs to be the same as the model in the view, or you need to use the [Bind(Prefix = "TourDate")] attribute and you also need to remove the [Bind(Include = "..")] attribute).
  2. Your not getting client side validation associated with your dropdownlists and in the POST method, if ModelState is invalid, your just redirecting (the user would just assume the object has been saved and not understand what is going on). You need to return the view so errors can be corrected, but in your case, the values the user has entered will be reset to the defaults (an annoying user experience).

Your editing data, so you should always use a view model and in the controller method you need to populate the SelectList's to account for initial values and edited values (for when you need to return the view). You code should be

public class TourDateVM
{
    [Required(ErrorMessage = "Please select a country")]
    [Display(Name = "Country")]
    public int? SelectedCountry { get; set; }
    [Required(ErrorMessage = "Please select a tour")]
    [Display(Name = "Country")]
    public int? SelectedTour { get; set; }
    [Required(ErrorMessage = "Please enter a start date")]
    [Display(Name = "Start date")]
    public DateTime? StartDate { get; set; }
    .... // ditto above for EndDate
    public IEnumerable<SelectListItem> CountryList { get; set; }
    public IEnumerable<SelectListItem> TourList { get; set; }
}

And in the controller

public ActionResult Create()
{
    TourDateVM model = new TourDateVM();
    ConfigureViewModel(model);
    return View(model);
}
[HttpPost]
public ActionResult Create(TourDateVM model)
{
    if (!ModelState.IsValid)
    {
        ConfigureViewModel(model);
        return View(model);
    }
    TourDate tour = new TourDate()
    {
        TourId = model.SelectedTour,
        StartDate = model.StartDate,
        EndDate= model.EndDate
    };
    db.TourDates.Add(tour);
    db.SaveChanges();
    return RedirectToAction("Index", "Home");
}
private ConfigureViewModel(TourDateVM model)
{
    var counties = db.Countries;
    model.CountryList = new SelectList(counties, "ID", "Name"); // adjust to suit your property names
    if (model.SelectedCountry.HasValue)
    {
        var tours = db.Tours.Where(e => e.CountryId == model.SelectedCountry);
        model.TourList = new SelectList(tours, "Id", "TourName");
    }
    else
    {
        model.TourList = new SelectList(Enumerable.Empty<SelectListItem>());
    }
}

and finally in the view (note that both dropdownlists need to be inside the <form> element)

@model TourDateVM
....
@using Html.BeginForm())
{
    ....
    <div class="form-group">
        @Html.LabelFor(m => m.SelectedCountry, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(m => m.SelectedCountry, Model.CountryList, "- Please select -", new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.SelectedCountry, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.SelectedTour, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(m => m.SelectedTour, Model.TourList, "- Please select -", new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.SelectedTour, "", new { @class = "text-danger" })
        </div>
    </div>
    ....
}

and the script should be

var url = '@Url.Action("GetT")'; // assumes its in the same controller
var tours = $('#SelectedTour');

$('#SelectedCountry').change(function() {
    tours.empty();
    $.post(url , { id: $(this).val() }, function(data) {
        if (!data) {
            return;
        }
        tours.append($('<option></option>').val('').text('- Please select -'));
        $.each(data, function(index, item) {
            tours.append($('<option></option>').val(item.Id).text(item.TourName));
        });
    });
})