Cómo crear un Cascading DropDownList en ASP.NET MVC
Seguramente, en algunos de nuestros desarrollos ASP.NET MVC, nos hemos visto en la necesidad de crear un formulario de tipo POST, que contenga listas desplegables (<select />
) relacionadas entre sí, en función del elemento seleccionado en una de ellas (Cascading DropDownList).
En este artículo veremos cómo crear un Cascading DropDownList de dos listas desplegables, cargando de forma dinámica (AJAX) la segunda lista, en función del elemento seleccionado en la primera.
Los Modelos de datos
Lo primero que debemos hacer, es crear los Modelos que proveerán de datos a las dos listas desplegables de nuestro ejemplo.
Como origen de datos, utilizaremos la base de datos de pruebas Northwind de Microsoft (Scripts disponibles para descargar al final del Post). Los Modelos de datos los generaremos a partir de las tablas Territories
y Region
, relacionadas entre sí por una clave foránea (FK_Territories_Region
) de la siguiente manera:
Lo que queremos reproducir, es la interacción entre el primer Modelo Region
(primera lista desplegable), tal que al seleccionar un elemento, solo se carguen en la segunda lista desplegable (Territories
) los elementos que estén relacionados por su clave foránea.
Crearemos entonces los siguientes Modelos de Datos en nuestra aplicación:
public class Region
{
public Region()
{
Territories = new HashSet<Territories>();
}
public int RegionId { get; set; }
[Required]
public string RegionDescription { get; set; }
// PROPIEDAD DE NAVEGACIÓN
public ICollection<Territories> Territories { get; set; }
}
public class Territories
{
[Key]
public string TerritoryId { get; set; }
[Required]
public string TerritoryDescription { get; set; }
public int RegionId { get; set; }
// PROPIEDAD DE NAVEGACIÓN
[ForeignKey("RegionId")]
public Region Region { get; set; }
}
El Controlador
En la Acción Index()
del Controlador (en este caso HomeController
), cargaremos los datos de la primera lista desplegable (Regions
) en la propiedad ViewBag
.
Nota: Para realizar este ejemplo, es necesario disponer de una base de datos con las tablas anteriormente mencionadas, así como tener instalado en nuestro proyecto el ORM Entity Framework 6 para el acceso a los datos. Para saber cómo crear una base de datos de pruebas con Entity Framework 6 en ASP.NET MVC, pueden aprender todo lo necesario en este artículo: Entity Framework 6 y Sql Server Compact CE en ASP.NET MVC 5.
El código para el Controlador HomeController
sería el siguiente:
public class HomeController : Controller
{
private AppDbContext _dbContext;
private List<SelectListItem> _regionsItems;
public ActionResult Index()
{
using (_dbContext = new AppDbContext())
{
// CARGAMOS EL DropDownList DE REGIONES
var regions = _dbContext.Regions.ToList();
_regionsItems = new List<SelectListItem>();
foreach (var item in regions)
{
_regionsItems.Add(new SelectListItem
{
Text = item.RegionDescription,
Value = item.RegionId.ToString()
});
}
ViewBag.regionsItems = _regionsItems;
}
return View();
}
// AQUÍ EL RESTO DE LAS ACCIONES...
}
Para recuperar de la base de datos los datos de la segunda lista desplegable (Territories
), crearemos una nueva Acción (GetTerritoriesList(int RegionId)
) que devolverá una estructura JSON (JsonResult
) en función de la Región seleccionada en la primera lista desplegable (int RegionId
).
public JsonResult GetTerritoriesList(int RegionId)
{
using (_dbContext = new AppDbContext())
{
var territories = _dbContext.Territories.Where(x => x.RegionId == RegionId).ToList();
return Json(territories, JsonRequestBehavior.AllowGet);
}
}
La Vista
En la Vista (Index.cshtml
) , crearemos las dos listas desplegables (DropDownList
):
<div class="row">
<div class="form-group">
@Html.DropDownList("ddlRegions", ViewBag.regionsItems as List<SelectListItem>,
" -- Seleccionar Regiones --", new { @class = "form-control" })
<br />
@Html.DropDownList("ddlTerritories", new List<SelectListItem>(),
" -- Seleccionar Territorios --", new { @class = "form-control" })
</div>
</div>
Como vemos en el código, la primera lista desplegable (ddlRegions
) carga todos elementos de la base da datos, y la segunda lista (ddlTerritories
), estará vacía hasta que se seleccione un elemento de la primera.
Recuperando los datos con jQuery - AJAX
En este punto, quedaría lo más importante: Recuperar los datos de la lista desplegable ddlTerritories
, en función del elemento seleccionado en la lista ddlRegions
mediante jQuery AJAX.
Nota: Por supuesto, tenemos que tener instaladas y referenciadas en nuestro proyecto las librerías jQuery, para habilitar entre otras las funcionalidades AJAX.
Para ello y por último, crearemos un script jQuery que llame mediante AJAX a la Acción GetTerritoriesList(int RegionId)
, y recupere en formato JSON los datos de la lista desplegable ddlTerritories
.
El código lo definiríamos en la sección @section scripts {...}
, y sería el siguiente:
@section scripts {
<script>
$(document).ready(function () {
$("#ddlRegions").change(function () {
$.get("/Home/GetTerritoriesList", { RegionId: $("#ddlRegions").val() }, function (data) {
// VACIAMOS EL DropDownList
$("#ddlTerritories").empty();
// AÑADIMOS UN NUEVO label CON EL NOMBRE DEL ELEMENTO SELECCIONADO
$("#ddlTerritories").append("<option value> -- Seleccionar Territorios de " + $("#ddlRegions option:selected").text() + " --</option>")
// CONSTRUIMOS EL DropDownList A PARTIR DEL RESULTADO Json (data)
$.each(data, function (index, row) {
$("#ddlTerritories").append("<option value='" + row.TerritoryId + "'>" + row.TerritoryDescription + "</option>")
});
});
});
});
</script>
}
Nuevo comentario
Comentarios
A mi me pasa lo mismo que a Cristian al querer pasar las selecciones de ambos DDL al controlador, hago agua, ya llevo varios dias... alguien tiene una idea?
He intentado de varias formas, pero me sigue dando los valores del select como "undefined", alguien me podría ayudar?
Y en caso de ser un Edit, como se devolvería el valor guardado en Región y Territorio sin que Territorio cambie y me pida seleccionar uno nuevo.
Para resolver el problema de que no muestra datos o muestra Undefined, en mi caso el return del contrlador es un poco diferente al del ejemplo:
Mi codigo => return Json(new SelectList(VigenciaList, "RowId", "Vigencia"));
Con esto retorno un Json con la estructura del SelectList {disabled, group, selected, text, value} luego en el script tengo lo siguiente:
Mi script => $("#Vigencia").append("<option value='" + row.value + "'>" + row.text + "</option>")
Noten que no hago referencia a los nombres "RowId" y "Vigencia" que retorna el Json, sino que utilizo "value" y "text"
Espero que les pueda ayudar a resolver este problema.
Si no les presenta los datos en el @Html.DropDownList("ddlTerritories", new List<SelectListItem>(),.... posiblemente les sirva poner esto db.Configuration.ProxyCreationEnabled = false; en GetTerritoriesList, así:
public JsonResult GetSubjectsList(int AreaID)
{
db.Configuration.ProxyCreationEnabled = false;
List<Subject> subjects = db.Subjects.Where(x => x.Area.AreaID == AreaID).ToList();
return Json(subjects, JsonRequestBehavior.AllowGet);
}
Hago todos los pasos como dice el ejemplo utilizando mis propias bases de datos pero no me devuelve en el row los valores que necesito
$.each(data, function (index, row) {
$("#ddlTerritories").append("<option value='" + row.TerritoryId + "'>" + row.TerritoryDescription + "</option>")
En mi ejemplo tengo los campos de la base de datos como ID y Descripción
$.each(data, function (index, row) {
$("#ddlTerritories").append("<option value='" + row.Id + "'>" + row.Descripcion + "</option>")+
Agradeceria alguna ayuda escribirme a chokisoft@gmail.com
No funciona completo solo muestra el dropdownlist de regiones. Luego no hace nada en mi caso al seleccionar territorios. Me imagino as llegó lo esta bien en el script
Hola, como podria implementar esta misma logica pero para 3 dropdown dependientes?
Excelente artículo, muchas gracias.
solo me muestra los datos de en este caso la region en la de territorios no me devuelve nada cheque todo y al parecer tengo todo bien pero no se en que me equivoque o si tengo mal algo
@Vicente Maldonado:
Gracias por tu comentario,
En principio la utilización de
JsonRequestBehavior.AllowGet
es necesaria, ya que la llamada Ajax que se está haciendo desde el Cliente es de tipo Http GET ($.get("/Home/GetTerritoriesList" )
).Lo único que se me ocurre es que estés utilizando ASP.NET Core, donde
JsonRequestBehavior.AllowGet
ya no es necesario especificarlo en la respuesta Json.buen día, He probado el código, funciona el llamado al public JsonResult GetTerritoriesList(int RegionId), aparentemente retorna datos, pero no los presenta en el @Html.DropDownList("ddlTerritories", new List<SelectListItem>(),.... solo muestra indefined.
Tuve que cambiar return Json(territories, JsonRequestBehavior.AllowGet); por return Json(territories)
Gracias por su ayuda.