mejor manera de cargar 2 menús desplegables en mvc

Así es como estoy cargando en el estado de carga de la página y en el menú desplegable de la ciudad:

Mi método de controlador :

Este es el primer método que llama cuando se carga la página.

public ActionResult Index() { var states = GetStates(); var cities = Enumerable.Empty(); ViewBag.States = states; ViewBag.Cities = cities; } private IEnumerable GetStates() { using (var db = new DataEntities()) { return db.States.Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() }); } } [HttpGet] public ActionResult GetCities(int id) { using (var db = new DataEntities()) { var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList(); return Json(data, JsonRequestBehavior.AllowGet); } } 

Mi vista :

 IEnumerable States = ViewBag.States; IEnumerable Cities = ViewBag.Cities; @Html.DropDownList("State", States, "Select State", new { onchange="loadCities(this)"}) @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"}) function loadCities(obj) { $.ajax({ url: "/Home/GetCities", data: { id: $(obj).val() }, contentType:"application/json", success:function(responce){ var html = 'Select City'; $(responce).each(function () { html += ''+this.Text+'' }); $("#ddlCity").html(html); } }); } 

¿Hay alguna forma mejor que cargar el estado y el menú desplegable de la ciudad?

 public class HomeController : Controller { public ActionResult Index(int id=0) { Person model = null; var states = GetStates().ToList(); var cities = Enumerable.Empty(); if (id > 0) { using (var db = new DataEntities()) { model = db.People.Include("City").FirstOrDefault(d => d.Id == id); if (model == null) model = new Person(); else { states.First(d => d.Value == model.City.StateId.ToString()).Selected = true; cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem { Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId }); } } } else { model = new Person(); } ViewBag.States = states; ViewBag.Cities = cities; ViewBag.Persons = GetPersons(); return View(model); } [HttpGet] public ActionResult GetCities(int id) { using (var db = new DataEntities()) { var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList(); return Json(data, JsonRequestBehavior.AllowGet); } } public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model) { // var employeeDal= new Emploee(); //employee.firstname=model. if (ModelState.IsValid) { var Id = model.Id; int.TryParse(Request["Id"], out Id); using (var db = new DataEntities()) { if (Id > 0) { var person = db.People.FirstOrDefault(d => d.Id == Id); if (person != null) { model.Id = Id; db.People.ApplyCurrentValues(model); } } else { db.People.AddObject(model); } db.SaveChanges(); } } if (!Request.IsAjaxRequest()) { ViewBag.States = GetStates(); ViewBag.Persons = GetPersons(); ViewBag.Cities = Enumerable.Empty(); return View("Index"); } else { return PartialView("_personDetail",GetPersons()); } } public ActionResult Delete(int id) { using (var db = new DataEntities()) { var model = db.People.FirstOrDefault(d => d.Id == id); if (model != null) { db.People.DeleteObject(model); db.SaveChanges(); } } if (Request.IsAjaxRequest()) { return Content(id.ToString()); } else { ViewBag.States = GetStates(); ViewBag.Persons = GetPersons(); ViewBag.Cities = Enumerable.Empty(); return View("Index"); } } private IEnumerable GetStates() { using (var db = new DataEntities()) { return db.States.ToList().Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() }); } } private IEnumerable GetPersons() { using (var db = new DataEntities()) { return db.People.Include("City").Include("City.State").ToList(); } } public ActionResult HomeAjax() { ViewBag.States = GetStates(); ViewBag.Cities = Enumerable.Empty(); using (var db = new DataEntities()) { var data = db.States.Include("Cities").Select(d => new { Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new { Id=x.Id,Name=x.CityName}) }).ToList(); ViewBag.CityStateJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data); } ViewBag.Persons = GetPersons(); return View(); } } @model IEnumerable 
@if (Model.Count() == 0) { } else { foreach (var item in Model) { } }
First Name Last Name Email City State Edit

No data available

@item.FirstName @item.LastName @item.Email @item.CityName @item.StateName @if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest()) { Update @Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions {OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" }) } else { @Html.ActionLink("Update", "Index", new { id = item.Id }) @Html.ActionLink("Delete", "Delete", new { id = item.Id }) }
@model Person @{ ViewBag.Title = "Home Ajax"; IEnumerable persons = ViewBag.Persons; IEnumerable States = ViewBag.States; IEnumerable Cities = ViewBag.Cities; IEnumerable fullStates=ViewBag.CityStates; } @section featured { } @section styles{ td,th { border:1px solid; padding:5px 10px; } select { padding:5px 2px; width:310px; font-size:16px; } } @section scripts{ @Scripts.Render("~/bundles/jqueryval") var jsonArray = @Html.Raw(ViewBag.CityStateJson) function clearValues() { $("input[type='text'],select").val(''); $("input[type='hidden'][name='Id']").val(0); } function loadCities(obj) { for (var i = 0; i < jsonArray.length; i++) { if (jsonArray[i].Id == parseInt($(obj).val())) { fillCity(jsonArray[i].Cities); break; } } } function Edit(obj, Id) { // alert("hi") $("input[type='hidden'][name='Id']").val(Id); var tr = $(obj).closest("tr"); $("#txtfirstName").val($("td[data-id='fn']", tr).text().trim()); $("#txtlastName").val($("td[data-id='ln']", tr).text().trim()); $("#txtemail").val($("td[data-id='email']", tr).text().trim()); var city = $("td[data-id='cn'] input[type='hidden']", tr).val(); var state; for (var i = 0; i < jsonArray.length; i++) { for (var j = 0; j < jsonArray[i].Cities.length; j++) { if (jsonArray[i].Cities[j].Id == parseInt(city)) { state = jsonArray[i].Id; break; } } if (state) { fillCity(jsonArray[i].Cities); break; } } $("#ddlState").val(state); $("#ddlCity").val(city); } function fillCity(obj) { var html = 'Select City'; $(obj).each(function () { html += '' + this.Name + '' }); $("#ddlCity").html(html); } function deleteSuccess(responce) { alert("record deleted successfully"); $("tr[data-id='" + responce + "']").remove(); } function insertSuccess() { alert("Record saved successfully"); clearValues(); } function showLoader() { $("#overlay").show(); } function hideLoader() { $("#overlay").hide(); } }

Add Personal Detail

@using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"})) { @Html.HiddenFor(m => m.Id);
  1. @Html.LabelFor(m => m.FirstName) @Html.TextBoxFor(m => m.FirstName, new { id = "txtfirstName" }) @Html.ValidationMessageFor(m => m.FirstName)
  2. @Html.LabelFor(m => m.LastName) @Html.TextBoxFor(m => m.LastName, new { id = "txtlastName" }) @Html.ValidationMessageFor(m => m.LastName)
  3. @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email, new { id = "txtemail" }) @Html.ValidationMessageFor(m => m.Email)
  4. @Html.Label("State") @Html.DropDownList("State", States, "Select State", new { onchange = "loadCities(this)", id = "ddlState" })
  5. @Html.LabelFor(m => m.CityId) @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id = "ddlCity" }) @Html.ValidationMessageFor(m => m.CityId)
}

Person List

@Html.Partial("_personDetail", persons)

Te acercas a usar ajax está bien, aunque recomendaría algunas mejores prácticas, incluyendo el uso de un modelo de vista con propiedades para StateID , CityID StateList y CityList , y utilizando discreto JavaScript en lugar de contaminar tu marcado con el comportamiento y generar el primero (“selecciona por favor” ) opción con un valor null lugar de 0 para que pueda usarse con el atributo [Required]

HTML

 @Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange @Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID? 

GUIÓN

 var url = '@Url.Action("GetCities", "Home")'; // use the helper (dont hard code) var cities = $('#CityID'); // cache the element $('#StateID').change(function() { $.getJSON(url, { id: $(this).val() }, function(response) { // clear and add default (null) option cities.empty().append($('').val('').text('Please select')); $.each(response, function(index, item) { cities.append($('').val(item.Value).text(item.Text)); }); }); }); 

Si estaba procesando varios elementos (digamos que le pedía al usuario que seleccionara las últimas 10 ciudades que visitó), puede almacenar en caché el resultado de la primera llamada para evitar llamadas repetidas, donde las selecciones pueden incluir ciudades del mismo estado.

 var cache = {}; $('#StateID').change(function() { var selectedState = $(this).val(); if (cache[selectedState]) { // render the options from the cache } else { $.getJSON(url, { id: selectedState }, function(response) { // add to cache cache[selectedState] = response; ..... }); } }); 

Finalmente, en respuesta a sus comentarios sobre cómo hacerlo sin ajax, puede pasar todas las ciudades a la vista y asignarlas a una matriz de javascript. Solo recomendaría esto si tiene algunos países, cada uno con algunas ciudades. Es una cuestión de equilibrar el ligero tiempo de carga inicial extra frente a la ligera demora en hacer la llamada ajax.

En el controlador

 model.CityList = db.Cities.Select(d => new { City = d.CountryID, Text = d.CityName, Value = d.Id }).ToList(); 

En la vista (script)

 // assign all cities to javascript array var allCities= JSON.parse('@Html.Raw(Json.Encode(Model.CityList))'); $('#StateID').change(function() { var selectedState = $(this).val(); var cities = $.grep(allCities, function(item, index) { return item.CountryID == selectedState; }); // build options based on value of cities }); 

Este es un enfoque correcto, pero puedes simplificar tu javascript:

 function loadCities(obj) { $.getJSON("/Home/GetCities", function (data) { var html = ''; $(data).each(function () { html += '' }); $("#ddlCity").html(html); }); } 

Otra simplificación posible: agregue el elemento predeterminado (Seleccionar ciudad) del lado del servidor, por lo que su javascript será más pequeño.

Así es como lo haría sin actualizar la página, suponiendo que la lista de ciudades no sea demasiado larga. Supongo que puedes crear un método GetStatesAndCities para devolver un diccionario.

 public ActionResult Index() { Dictionary> statesAndCities = GetStatesAndCities(); ViewBag.StatesAndCities = Json(statesAndCities); } 

Luego en la vista:

 var states = JSON.parse(@ViewBag.StatesAndCities); function loadCities(obj) { var cities = states[$(obj).val()]; var html = ''; $(cities).each(function () { html += '' }); $("#ddlCity").html(html); } 

De esta forma, cuando se cambia el estado, el campo de las ciudades se actualiza inmediatamente sin necesidad de callback.

descargo de responsabilidad: esta no es una respuesta de código, hay muchas otras respuestas.

Creo que es la mejor manera de mantenerte feliz de separar las páginas de la interfaz de usuario de los datos => convertirlos en llamadas API:

  • / GetCities
  • / GetStates

Ahora puede simplemente dejar la selección vacía en Razor renderizando la página. Y use un complemento de Jquery / Bootstrap para crear un cuadro de selección de AJAX.

De esta forma, cuando el usuario deja de escribir su búsqueda, esta cadena de búsqueda se puede enviar con la llamada AJAX (por ejemplo: /GetStates?search=test ) y luego se puede enviar un pequeño conjunto de resultados al sitio web.

Esto da:

  • Mejor separación en el código del servicio
  • Mejor experiencia de usuario.
  • Cargas de página más pequeñas (ya que ya no envía todas las opciones al usuario cuando solicita la página, solo cuando abre el cuadro de selección).

¿Qué hay de usar Knockout?

Knockout es una biblioteca de JavaScript que le ayuda a crear interfaces de usuario de pantalla y editor ricas y con capacidad de respuesta con un modelo de datos subyacente limpio

Tienes que usar ajax para tus ciudades. Pero con knockout no necesitas escribir

var html = ''; $(responce).each(function () { html += ''}); $("#ddlCity").html(html);

en tu javascript.Knockout lo hace simple.

Simplemente puede escribir:

  function CityModel() { var self = this; // that means this CityModel self.cities = ko.observableArray([]); self.getCities = function () { $.ajax({ url: "/Home/GetCities", data: { id: $(obj).val() }, contentType: "application/json", success: self.cities }); } } ko.applyBindings(new CityModel()); 

eso es todo. Pero debes unir tus datos en elementos html. En lugar de usar: @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})

Puedes usar:

   

o puedes mezclar la razor y el knockout:

 @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity",data_bind:"options:cities,optionsValue:\"Id\",optionsText:\"CityName\""}) 

Una cosa más que tiene que llamar a GetCities cuando el State cambia, puede:

 @Html.DropDownList("State", States, "Select State", new {data_bind:"event:\"change\":\"$root.GetCities\""}) 

No te asustes con \ “\” cosas porque " es un personaje de escape y tenemos que decir a la máquina de afeitar que quiero usar” usando \ antes.

Puede encontrar más información sobre knockout: Knockout

Y mezclar con la maquinilla de afeitar: Razor y Knockout

Pd: sí, usar knockout es suspendernos de Razor y Mvc. Tienes que escribir otro ViewModel. Pero así las situaciones ko son útiles. Mezcla de afeitar y knockout es otra opción para usted.