viernes, 27 de diciembre de 2013

[ASP.NET MVC] Carga DropDownList dependientes (cascada o secuencial)

 

Introducción


Es muy común en las aplicaciones necesitar de controles que dependan unos de otros para cargar los ítems que serán desplegados al usuario.

En este articulo explicare como llevar a cabo esta tarea implementando la carga en cascada de combos y listas, además poder tomar en cada post la selección que realiza el usuario.

El ejemplo permitirá seleccionar una región la cual será utilizada como filtro para cargar los territorios, este ultimo cargar una lista de empleados, el usuario seleccionara uno o mas de la lista.

 

image

 

Modelo de datos


Haremos uso del siguiente modelo de datos el cual define las clases que serán mapeadas a las tablas utilizando Entity Framework

Vemos como interactúan las entidades para permitir la asociaciones que las vinculan.

image

 

Modelo de Vista


Para poder interactuar con al view de mvc creamos una clase que actuara de modelo, esta nos permitirá definir los ítems de las listas que desplegaremos al usuario.

En el modelo tendremos propiedades simples que contendrán la selección del usuario y también lista que nos permitirán cargar los dropdownlist y listbox que presentaremos al usuario.

 

image

Las propiedades de lista se definen del tipo SelectList, de esta forma desde el controlador indicamos cuales serán las propiedades que usara la lista como value y display en el dropdownlist

 

Lista de Regiones


Al iniciar la aplicación el primer controlador que se ejecuta es el Index(), en este invocamos al repositorio para obtener la lista de regiones

image

 

Creamos una instancia de la clase Model que el view requiere, a esta le asignamos la lista de regiones que mostrara en el combo

En la view se carga el dropdownlist y se controla con la ayuda de jquery cuando el usuario cambia la selección, lo cual produce el post del form.

En la imagen podemos ver como se relacionan los id del dropdownlist y del form con el evento change() de jquery para generar el submit al action del controlador

 

SNAGHTML146b09bb

 

Lista de Territorios


Al seleccionar una región se invoca el action definido en el BeginForm()

SNAGHTML14776cd1 

Si hacemos coincidir el name del control dropdownlist con el del parámetro del action este recibirá el valor. Usaremos la selección para filtrar la lista de territorios.

 

Lista de Empleados


Al seleccionar un ítem del combo de territorios se dispara el submit del forma que invoca el action del controlador, en este caso se pasaran como argumento el id de la región y del territorio para poder filtrar los datos y cargar las listas

 

SNAGHTML14cd3b57

Seguramente se habrá notado la definición de controles Hidden que permiten enviar en cada submit del form los datos seleccionados en una acción anterior

 

Selección de empleados


Cuando se selecciona los empleados de la lista y se presiona el botón se realiza el submit enviando la la selección del listbox al action

SNAGHTML14d57876

En cada invocación a los action se vuelve a crear una instancia del model asignando la selección y cargando las listas que requiere el view

 

Código


Se utilizo Visual Studio 2012, la base de datos de encuentra en la carpeta App_Data, se utilizo Sql Server Express 2012

[C#]
 

51 comentarios:

  1. Genial, tengo una consulta, qué habría que cambiar en el código para hacer lo mismo en VS2010??

    Estaré atenta a sus respuestas, gracias!

    ResponderEliminar
  2. hola Gayle

    pero en VS 2010 tienes el temaplate para asp.net mvc ?

    http://www.asp.net/mvc/mvc4

    como veras en el titulo "Install ASP.NET MVC 4 for Visual Studio 2010" alli menciona que puedes instalarlo

    depsues el resto es identico

    saludos

    ResponderEliminar
  3. Ok, gracias. Ya lo tengo, ahora tengo otra consulta, cómo puedo cargar los dropdownlist en un popup con los datos de un jqgrid, necesito esto para que el usuario edite. Espero haberme explicado.

    Saludos!

    ResponderEliminar
  4. Hola Leandro,
    primeramente agradeceré por todo el material que publicas es muy útil y felicitarte por tu nivel de profesionalidad es increíble. Me gustaría poder contactarte vía skype o algún medio de comunicación mas directo podría ser email tengo una duda te agradeceria tu ayuda.

    Gracias y saludos.

    Mi email adw-cr@hotmail.com

    ResponderEliminar
  5. Hola Leandro, una consulta como se puede hacer esto mismo (llenado de dropdownlist), pero con origen de datos un método de un web service remoto que tiene acceso a una Base de datos...no sé si me explico bien... estoy usando visual studio 2010, mvc3 y vb.net como lenguaje..

    de antemano muchas gracias
    Saludos!

    ResponderEliminar
  6. hola Edgardo

    pero el web service remoto no lo deberias invocar desde el controller

    digo desde el action donde armas el model que asignas a la view invocarias el web service, tmarias los items y cargarias la entidad para asignar el modelo

    saludos

    ResponderEliminar
  7. Estimado Leandro y como sería este mismo escenario pero usando JQuery???

    ResponderEliminar
  8. hola Dennis

    podrias ser bastente parecido solo que usarias el Ajax.BeginForm()

    Simple Implementation of MVC Cascading Ajax Drop Down

    saludos

    ResponderEliminar
  9. Leandro, tema un poco aparte, pero relacionado con MVC: debo mostrar una vista, ej., Create, donde deben ingresar DatoA, DatoB, entre otros. En la misma vista Create se debe calcular DatoC, mismo que se obtiene de DatoA*DatoB. ¿Algún material que pudieras recomendar o algunos tips específicos?. De antemano agradezco tu aporte.

    ResponderEliminar
  10. hola CESAR

    si defines una clase dentro de Model que defiens una propiedad de solo lectura que tenga


    public class XXModel{

    //propiedades

    public int DatoC{
    get { return this.DatoA * this.DatoC; }
    }
    }


    la puse como propiedad readonly porque es calculada en base a las otras dos propiedades de la clase modelo que usarias en la view

    saludos

    ResponderEliminar
  11. Gracias, me funcionó con propiedad solo de lectura. ¿Que tendría que hacer si quiero guardar el resultado en base de datos?. Tener en cuenta que me refiero a campos calculados.
    Disculpas que soy nuevo en esto. Se que para tu experiencia eso no es nada, pero para mi, mmmmm.
    De nuevo, gracias por tu valioso aporte.

    ResponderEliminar
  12. He leído variedad de materiales cortos de MVC. Entre todos he logrado crear un sistema con operaciones CRUD hacia sus tablas. Lo que no logro hacer, de forma natual y fluida, es calcular valores para ciertas propiedades que quiero salvarlas en bases de datos. No he encontrado ningun material que lo explique de forma clara a mi nivel de conocimiento. Agradecería que remitieras a algun material o me ofrezcas algunos tips. Gracias de antemano. Aprecio tu apoyo.

    ResponderEliminar
  13. hola CESAR

    si quieres guardar la informacion simplemente tomas el dato de la propiedad y lo asignas al parametro del INSERT

    igual imagino deberias definir esa propiedad tambien en las clases de tu negocio, ya que actualmente esta solo en la clase del Model

    no encuentras info porque los datos calculados no se persisten, es mas si estudias las formas de normalizacion de la base de datos te dira que no debes grabar datos calculados

    saludos

    ResponderEliminar
  14. Muy bien Leandro. Debo asimilarlo de esa forma: no grabar datos calculados. Con esto me salta la inquietud acerca de producir otro tipo de información, digamos que estadistica (totalización). Está claro que debo replantearme como se debe procesar datos y producir información útil. Tu respuesta oportuna me pone en la ruta correcta para continuar auto-capacitandome. Tengo el reto personal de desarrollar un sistema con MVC, y casi lo estoy logrando. Tu aporte me es muy valioso. Simplemente... GRACIAS MIL.

    ResponderEliminar
  15. Hola ! una pregunta, tendras ejemplo de de combos dependientes pero con mvc4 y que solo se obtegan los datos de una tabla (ubigeo)

    ResponderEliminar
  16. hola Hebert

    no importa la version de mvc los ejemplos de estos articulos aplican perfectamente

    lo que no entendi es lo de la unica tabla, eso es muy raro, si hay al menos dos combos imagino cada uno deberia tomar los datos de su propia tabla

    saludos

    ResponderEliminar
  17. Leandro que buen articulo. gracias por tanta onda

    ResponderEliminar
  18. Leandro como se hace para q en el submit valla el ViewModel? gracias

    ResponderEliminar
  19. hola Ariel

    un viewmodel? se tratata de una clase definida en la carpeta Model

    una accion de submit invoca un action que esta en el controller, a la vuelta del action este define que view se debe renderizar

    te refieres al modelbinding de los datos de la view con el parametro model del action

    saludos

    ResponderEliminar
    Respuestas
    1. Buenas Tardes Seños Leandro, Temgo un pro0blema con mi aplicacion, tengo 3 dropdownlist anidados, el problema es que al parecer el selectedindexchanged no me esta funcionando al cambiar de valor en el primer dropdown, y cuando le pongo un autopostback me refresca la pagina pero sin haber guardado los cambios que hice, osea la seleccion que habia hecho, no encuentro una solucion por que mi codigo lo veo bien , pero el problema al parecer esta en el selected index changed
      protected void Page_Load(object sender, EventArgs e)
      {
      if (IsPostBack == false)
      {
      BTNREGNSOC.Enabled = false;
      TXTFECHA.Text = DateTime.Now.ToShortDateString();

      dpldepar.DataTextField = "Descripcion";
      dpldepar.DataValueField = "Id";
      dpldepar.DataSource = _depan.TraerTodos();
      dpldepar.DataBind();

      ListItem listI = new ListItem("**Seleccione**", "0");
      dpldepar.Items.Insert(0, listI);
      dplprov.Items.Insert(0, listI);
      dpldepar.SelectedIndex = 0;
      dplprov.SelectedIndex = 0;




      }
      }

      protected void dpldepar_SelectedIndexChanged(object sender, EventArgs e)
      {
      depa.Id = int.Parse(dpldepar.SelectedValue);

      dplprov.DataTextField = "Descripcion";
      dplprov.DataValueField = "Id";

      dplprov.DataSource = _provn.TraerUnoPorId(depa);
      dplprov.DataBind();
      }

      Eliminar
    2. hola Angel

      en el Page_Load usa if (!IsPostBack) queda mas claro

      despues lo que no entiendo es donde instancias la variable "depa" que asignas el Id
      si usas
      var provList = _provn.TraerUnoPorId(depa);
      y pones un breakpoint puedes evaluar que obtienes items que mostrar

      saludos

      Eliminar
  20. Hola Leandro, en mi caso tengo resolver este mismo problema y no encuentro la salida. Estoy usando Bootstrap con MVC5 y tomando datos recibidos por Referencia desde un WCF Rest.

    Agradezco tu comentario para ayudarme a desatar el nudo al problema.

    ResponderEliminar
  21. Hola leandro, gracias por tu tutorial fantástico, me ha venido genial !! pero al guardar los valores de los dropdownlist en la tabla de mi base de datos, no me lo está haciendo bien, me dice:

    El diccionario de parámetros contiene una entrada NULL para el parámetro 'cod_sede' del tipo que no acepta valores NULL 'System.Int32' del método 'System.Web.Mvc.ActionResult AreaList(Int32)' en 'MvcIntranet.Controllers.UbicacionUsuarioController'. Un parámetro opcional debe ser un tipo de referencia, un tipo que acepte valores NULL o debe declararse como parámetro opcional.

    Me podrias ayudar?

    ResponderEliminar
    Respuestas
    1. hola
      Entiendo que no llega a ingresar deberias validar que el "name" del dropdownlist sea "cod_sede" para que model binding pueda mapear el dato seleccionado con el parametro del action
      saludos

      Eliminar
  22. @Html.DropDownListFor(model => model.cod_sede, Model.SedeList, "--Seleccione Sede--") ??? donde se le asigna el name? qué torpe soy, perdona!! :(

    ResponderEliminar
  23. @model MvcIntranet.Models.areas

    @{
    ViewBag.Title = "Create";

    @using (Html.BeginForm("AreaList", "UbicacionUsuario", FormMethod.Post, new { @id = "SedeForm" }))
    {

    @Html.LabelFor(model=>model.cod_sede)
    @Html.DropDownListFor(model => model.cod_sede, Model.SedeList, "--Seleccione Sede--")

    }



    @using (Html.BeginForm("AreaList", "UbicacionUsuario", FormMethod.Post, new { @id = "AreaForm" }))
    {
    @Html.HiddenFor(model => model.cod_sede)

    @Html.LabelFor(model => model.cod_area)
    @Html.DropDownListFor(model => model.cod_area, Model.AreaList, "--Seleccione Area--")

    }

    @using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    @Html.EditorFor(model => model.cod_sede)
    @Html.EditorFor(model => model.cod_area)


    < type="submit" value="Crear" /> | @Html.ActionLink("Volver", "Index")


    }



    @section scripts
    {

    $(function() {

    $("#cod_sede").change(function () {

    $("#SedeForm").submit();

    });
    });



    }

    //---------------------------------------------------------------------------------------------------------------------------------

    //
    // GET: /UbicacionUsuario/Create

    public ActionResult Create()
    {
    areas model = new areas()
    {

    SedeList = new SelectList(db.sedes, "cod_sede", "Sede")

    };


    return View(model);
    }

    [HttpPost]
    public ActionResult AreaList(int cod_sede)
    {
    var sedeList = from q in db.sedes select q;

    var areaList = from p in db.areas where p.cod_sede == cod_sede select p;

    areas model = new areas()
    {

    SedeList = new SelectList(sedeList, "cod_sede", "Sede"),
    cod_sede = cod_sede,
    AreaList = new SelectList(areaList, "cod_area", "Area")
    };

    return View("Create", model);
    }



    //
    // POST: /UbicacionUsuario/Create

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(ubicacion_usuarios ubicacion_usuarios)
    {
    if (ModelState.IsValid)
    {
    db.ubicacion_usuarios.Add(ubicacion_usuarios);
    db.SaveChanges();
    return RedirectToAction("Index");
    }

    ViewBag.cod_usuario = new SelectList(db.usuarios, "cod_usuario", "cod_usuario", ubicacion_usuarios.cod_usuario);
    ViewBag.cod_area = new SelectList(db.areas, "Area", "cod_area", ubicacion_usuarios.cod_area);
    ViewBag.cod_sede = new SelectList(db.sedes, "Sede", "cod_sede", ubicacion_usuarios.cod_sede);


    return View(ubicacion_usuarios);
    }

    ¿Qué hago mal ?

    ResponderEliminar
  24. He añadido @Html.ValidationMessageFor(model => model.cod_sede) pero tampoco me funciona

    ResponderEliminar
  25. Creo que mi fallo está en que tengo un submit pero tres forms, como puedo hacer para que al pulsar sobre el botón me guarde todo?
    Lo que introduzco en cada form?

    Gracias!!

    ResponderEliminar
  26. Leandro, ya me salió todo, jeje, qué pesado soy !!!
    Ya funciona todo correctamente.

    Un saludo!!

    ResponderEliminar
    Respuestas
    1. hola,
      disculpa por no haber dado una respuesta antes
      bueno me alegro que pudieras resolverlo, cuando tienes una urgente podrias plantearlo en el foro de msdn
      ASP.NET MVC foro
      saludos

      Eliminar
  27. Buenos dias, una consulta, si al seleccionar un elemento del combo, en vez de llenar otro, mejor cambiara la informacion que devuelve el controlador que por lo general el lo imprime en el @foreach (var item in Model) {} como se haria? y para enviarlo de vuelta al controlador se recibiria como parametro en un FormCollection?

    ResponderEliminar
    Respuestas
    1. hola
      No se si entendi del todo el problema, pero en el evento del combo al action podrias enviar a la view los datos en el modelo, pero deberia ser una entidad algo mas compleja que retorne una lista para poder recorrer en el foreach
      Lo que no entendi es que quieres que reciba el action del controller, porque si puedes hacer que aplique el model binding no necesitas usar el FormController
      Lo que necesitas es enviar al action lo mismo que listas en el foreach ?
      saludos

      Eliminar
  28. Hola Leando!

    Me encuentro probando AngularJS con ASP.NET MVC (por las ventajas de AngularJS que he leido). Pero he tenido problema para enlazar con datos. Muestra esto: {{ tabla.dato}}, en vez del dato que debería mostrar.

    En los ejercicios que he seguido (varios), tanto material escrito como vídeo, todos tienen un final feliz, En ninguno de los ejercicios encuentro la causa de mi problema, aunque, según yo, los sigo al pie de la letra.

    ¿Tenés algún consejo que darme?. Te lo agradeceré.

    César Rivera

    ResponderEliminar
    Respuestas
    1. hola
      Partamos de la base que {{...}} no es bindeo, sino que solo muestras el dato, para bindear deberias usar el ng-model
      Ahora bien lo que habria que ver es como defines ese dato dentro del scope
      es mas estas definiendo el ng-app y el ng-controller ?
      recuerda validar que los .js de angular se esten cargando en la pagina, puedes usar el developer tools del browser al cual accedes con F12 y con la solapa "network" ver si resuelve los .js
      saludos

      Eliminar
  29. Hola yo tengo una duda tengo un modelo que a su vez contiene una lista de otro modelo pero cuando hago el HttpPost solo puedo recuperar los datos del modelo primario y la propiedad que contiene la lista con el modelo secundario viene como nula.
    Podrian ayudar me a entender por que viene nula y como puedo hacer que un modelo en una de sus propiedades me retorne una lista de otro modelo
    Gracias!!

    ResponderEliminar
    Respuestas
    1. hola
      como representas en la view esa lista del otro modelo? porque recuerda que en el post se va a cargar si esa info se enviar como parte de request
      Sera el model binding quien complete la entidad y sus propiedades si es que los nombre coinciden
      saludos

      Eliminar
  30. Hola Leandro Creo yo tengo un problema similar .....
    Tengo esta clase, que es mi DTO y es un mapeo de una tabla de BD

    public class Enrollment
    {
    public Course IdCourse { get; set; }

    public Person IdPerson { get; set; }

    public float Score { get; set; }
    }

    En el controller obtengo IdCourse e IdPerson que sus valores van a caer en un dropdownlist para mostrar y Score es un Textbox
    Agrego una vista fuertemente tipada a base de este modelo se llama ADD mi Vista
    Pero al generarme la vista solo me incluye la propiedad Score, y
    las propiedades IdCourse e IdPerson yo las incluyo a mano, e incluso
    estos valores los muestro en un dropdownlist a travez de un ViewBag y
    si despliega los valores, pero al momento de recuperarlos en el controller
    solo me recupera el valor de el campo Score y el IdCourse e IdPerson me aparecen en Null
    Cambie las propirdades del modelo a:

    public class Enrollment
    {
    public int IdCourse { get; set; }

    public int IdPerson { get; set; }

    public float Score { get; set; }
    }

    y para este modelo SI¡¡ me recupera los valores, entonces la cuestion es:
    ¿Como lograr que en la vista puedan convivir estas propiedades con tipo de dato de otro modelo?
    y que en el controller se recuperen los datos tal cual se manejan en el modelo?

    Si me puedes ayudar por fa ¡¡

    Saludos desde mexico.

    ResponderEliminar
    Respuestas
    1. hola
      Como defines en la view el name de los input que toma los valores de IdCourse y IdPerson ? porque alli esta la clave
      Cuando se realzia un post a un action el atributo "name" del textbox o dropdownlist debe coincidir con la propiedad de la clase para que el model binding realice el mapping automaticamente
      Analiza el html fgenerado en el browser y valida el name de estos combos
      saludos

      Eliminar
  31. hola muy buenos tus tutoriales, tengo una duda estoy haciendo un proyecto y necesito utilizar un dropdownlist, tengo una tabla tipo_habitacion y otra habitacion necesito cargar en el dropdownlist el nombre de los tipos de habitacion y obiamente mandarle el codigo para poder registrar una habitacion ya que esta tabla tiene como foranea el id tipo de habitacion, el problema esque estoy haciendo un proyecto mvc4 y estoy utilizando clases de linq to sql con procedimientos almacenados y no he podido hacerlo ya que este tuto esta diferente por favor si me puedes ayudar :(

    ResponderEliminar
    Respuestas
    1. hola
      La verdad no recomendaria usar las entidades de linq directamente como modelo de las view de mvc, justamente por el problema que estas teniendo, no puede personalizar los datos que al view requiere.
      Lo que se recomienda es que crees un clase en la carpeta Model de mvc con algo como ser HabitacionViewModel y uses esa clase en la view, desde el controller mapeas los datos de linq con esa clase y la usas de intermediaria.
      De esta forma podras adapatar las listas y propeidades para poder armar la estructura anidada de combos que necesitas.

      Eliminar
    2. hola gracias por responder, perdon mi ignorancia la verda es que soy nuev en esto de asp y la verda aun no comprendo muy bien loq debo d hacer podrias darme un peqeño ejemplo de como iria el modelo y el controlador por favor

      Eliminar
    3. hola
      Plantea la pregunta en el foro de msdn
      asp.net mvc foro
      poner codigo por este medio es complicado
      saludos

      Eliminar
  32. Amigos Una pregunta es que voy a crear un formulario de registro en el cual incluyo un drodownlist, pero siempre me sale: There is no ViewData item of type 'IEnumerable' that has the key 'ClienteId'. no he podido con este error me podrian ayudar gracias....

    ResponderEliminar
  33. Amigos Una pregunta es que voy a crear un formulario de registro en el cual incluyo un drodownlist, pero siempre me sale: There is no ViewData item of type 'IEnumerable' that has the key 'ClienteId'. no he podido con este error me podrian ayudar gracias....

    ResponderEliminar
  34. Hola, alguien me podría explicarme por favor que significa x => x.
    Donde se declara es ta variable 'x'?

    ResponderEliminar
    Respuestas
    1. hola
      Lo que alli tienes es una expresion lambda, pero no esta completa porque te falta indicar el objeto con el cual operas.
      Expresiones lambda (Guía de programación de C#)
      saludos

      Eliminar
    2. Hola Leandro, gracias por tu respuesta...
      Podrías ayudarme como puedo implementar este dropdownlist en una solución que no usa Entity Framework. Tengo mi capa entidades, acceso a datos y el proyecto web con los controladores y vistas

      Eliminar
    3. hola
      Pero si la capa de datos retorna una List<> de una clase que definas puedes asignarla al datasource del dropdownlist, entiendo que ado.net conoces y sabes como usar el reader para cargar una lista populando las clases
      saludos

      Eliminar
    4. Estoy usando Html.DropDownListFor, tambien se puede asignar a un datasource?

      Eliminar
  35. Hola como podria realizar este mismo ejemplo pero no por CodeFirst?

    ResponderEliminar
  36. Hola Leandro...

    Sabes recién estoy comenzando en este mundo, programando en C# con MVC, pero todos los tutoriales que he visto, ninguno de ello explica como se deben hacer las cosas, claro todas la programaciones son diferentes pero segun entiendo en MVC no tanto. Cuando reviso el codigo para cargar un simple combobox en cascada..leo y leo y no entiendo... ya que nadie se da el tiempo de poner y describir donde debe ir cada instruccion, me explico... este codigo debe ir en el Controller... este codigo se debe crear una clase dentro de esta de esta etiqueta, este modelo se crea aca y se llama asi...para que sea llamado por esta otra clase...etc...NADIE DICE O EXPLICA ESTO ....PORQUE..?

    Seguire tratando de entender...

    Saludos desde Santiago de Chile.
    Atte.

    Rodrigo Jorquera R.

    ResponderEliminar