En el desarrollo de aplicaciones, a menudo nos vemos en la necesidad de trabajar con estructuras en las que se solicitan colecciones de elementos (algo como un maestro detalle). El ejemplo típico es con una “Factura” o una “Orden de compra”, en la que se cuenta con un encabezado (maestro) de la Factura que contiene número, fecha, datos del cliente, etc. y el detalle de la factura contiene n elementos que corresponden a los datos del producto: cantidad, precio, sub-total, etc.
En este post voy a explicar como poder manejar este tipo de situaciones en una aplicación web (específicamente en una aplicación de ASP.Net MVC4) pero sin la necesidad de trabajar con extensiones de JavaScript ni nada por el estilo.
Código Fuente
El código fuente de ejemplo que muestro en este post estará disponible en mi repositorio de ejemplos en github en la ruta /dotNet/MaestroDetalleMVC4-01.
El Modelo
Para las entidades (en este caso en particular nuestros modelos) que corresponden al dominio de nuestra aplicación contaremos con las siguientes:
La representación de estas entidades en nuestro modelo relacional de base de datos podría verse de la siguiente forma:
El controlador
Trabajemos en nuestro controlador:
Basicamente lo que hacemos en el Action Create
es solicitar un parámetro accion
que nos va a indicar si hay que eliminar un detalle con base a su indice (eliminar-detalle-idx
), agregar uno nuevo (agregar-detalle
) o si viene null
es porque hay que intentar crear la factura con la información indicada.
Nota: en
eliminar-detalle-idx
elidx
es el indice o posición del detalle que se quiere eliminar por ejemplo si queremos eliminar el tercer detalle de la lista, comoaccion
se enviaráeliminar-detalle-2
.
Las vistas
Aquí es en realidad donde se tiene que trabajar con cuidado, ya que ASP.Net MVC4 cuenta con varias convenciones para realizar el binding entre nuestros modelos y la información enviada por el usuario por medio del POST de HTTP.
La primer convención que podemos notar en la vista, convención que quizás ya conozcan, es la que indica que el valor que se coloque en el atributo name
de nuestros items corresponde a los nombres de las propiedades de nuestro modelo. por ejemplo:
Si yo tengo un formulario que posee un input similar a este:
y tengo una clase que tiene la propiedad:
Entonces, cuando el usuario haga submit
del formulario, MVC asignara el valor ingresado en el input[name=edad]
a la propiedad Edad
de nuestro modelo.
Otra convención indica que si mi propiedad es de un tipo complejo, el nombre de mi input debe indicar la ruta completa de la propiedad a la que queremos hacer binding, por ejemplo:
Si yo tengo una clase que tiene una propiead de un tipo complejo:
Y nuestra clase Direccion
es algo como esto:
y si en nuestro formulario queremos solicitar el valor de la propiedad Pais
de la propiead DireccionDeCasa
de nuestro modelo, nuestro input tendría que ser algo como esto:
Entonces al hacer el submit de nuestro formulario, MVC creará una instancia nueva de Direccion
en la propiedad DireccionDeCasa
de nuestro modelo, con el valor del input[name=direcciondecasa.pais]
en la propiedad Pais
de esa instancia.
Excelente! verdad? olvidemonos ya de los
Request.QueryString["pais"]
o de losRequest.Post["edad"]
o de losRequest["algo"]
, ASP.Net MVC4 nos ayudó mucho con este trabajo de autómatas.
Ok, ahora ¿qué pasa con las colecciones Arrays
, List
, Collection
, Enumerable
?
Bueno, para esto existe otra convención que me dice que si yo tengo una propiedad que representa a un conjunto de datos, por ejemplo:
entonces yo puedo asignarle valores a ese arreglo simplemente asegurandome de que el input, aparte de tener el mismo nombre que la propiedad, tenga el índice encerrado entre corchetes []
. Por ejemplo:
En este ejemplo específico tenemos cuatro inputs
con name="calificaciones[]"
por lo que MVC creara una array de enteros de 4 posiciones con los valores de cada input colocados en cada una de las posiciones; esto quiere decir que si el usuario ingresa en el primer input el valor 1
, en el segundo el valor 2
, etc, al final recibiriamos en la propiedad Calificaciones
de nuestro modelo un array como este:
Pero… ¿y si mi propiedad es un conjunto de datos de tipos complejos, cómo le hago?
Pues… combinamos ambas convenciones, veamos la siguiente propiedad:
y queremos solicitarle al usuario el pais de cada direccion, solamente despues de los corchetes []
colocamos el nombre de la propiedad en la que queremos recibir el valor.
Nota: Vale la pena mencionar que todo esto lo logra el framework de ASP.Net MVC4 gracias a las implementaciones de la interfaz IModelBinder, espero poder hablar de ella en otro post.
Bueno!, ya habiendo explicado un poco todo este “relajo”, aqui les va el codigo de las vistas, la importante o relevante a este post es la vista Create.cshtml.
¿Cómo sabe MVC a que boton le dimos clicsi todos son input[type=submit]
?
Bueno, bueno, este es un principio de los formulario HTML como tal, que indica que si el input es de tipo submit
o button
se enviará únicamente el valor del boton al cual se le dió clic.
Conclusiones
Aunque funciona con esta forma de implementar un maestro-detalle, quizás no es la mas “adecuada” ya que ignoramos cosas como las validaciones del lado del cliente por ejemplo.
Adicionalmente a esto tambien la complegidad de nuestro Controllador se incrementa, ya que le damos mayor responsabilidades que las que le competen. Incluso nuestro Action Crate hace mas que lo que dice que hace, el action create se encarga de asignar detalles a la factura, y cosas por el estilo.
La responsabilidad de mostrar los elementos que necesita el usuario para ingresar la información tendría que ser de la Vista y no del controlador.
El objetivo de este post no es que elaboremos un Maestro-Detalle con esta técnica, sino que solamente dar las bases de como funciona internamente el Framework de Asp.Net MVC4 en estos escenarios.
Pero no se preocupen en el siguiente POST ya vamos a involucrar un poco a nuestro buen amigo JavaScript para delegar la responsabilidad que tiene ahorita el Controlador hacia la vista.
Despedida
Espero que les sea de utilidad esta información y nos vemos en el próximo artículo donde hablaremos un poco mas de este tema.
Saludos y hasta la próxima.