ASP.NET MVC | Cómo enviar modelo con JavaScript desde un formulario a una acción

Escenario

Supongamos que tenemos un formulario en una vista de una aplicación web realizada con ASP.MVC y necesitamos enviar los datos, que corresponden a un modelo de la aplicación, a nuestra acción del controlador. ¿Cómo realizamos esta tarea? Seguro que se nos ocurren ideas «liosas» para realizar esta operación pero generalmente optamos por lo más obvio que, además, no es óptimo ni flexible o no aprovechamos las facilidades que nos ofrece el ModelBinding.

Para explicar las posibilidades, quiero exponer las dos opciones que me he encontrado con más regularidad (por orden de veces que lo he visto) antes de exponer la que a mi parecer es la opción más óptima y os explicaré por qué.

Solución NO óptima 1

Lo que más he visto hacer, es lo más costoso y menos flexible que se podría hacer y no es otra solución que cambiar los parámetros de la acción del controlador por los campos del modelo y, en código JavaScript, recoger cada uno de esos campos por separados y mandarlos en los datos de la acción «Ajax». Esto no es en ningún momento óptimo ni bueno aunque funcione porque no es nada flexible ante cambios con lo que si añadimos una nueva propiedad, tendremos que cambiar todo nuestro código para poder enviarla, recibirla y validarla.

Acción del controlador

  1. [HttpPost] 
  2. public JsonResult Create(string brand, string model, …) {  
  3.     try {  
  4.         if (string.IsNullOrEmpty(brand) || string.IsNullOrEmpty(model) || …) return false;  
  5.         var car = new Car {  
  6.             Brand = brand, Model = model, // …  
  7.         };  
  8.         car.Status = «Solicitados»;  
  9.         return Json(true);  
  10.     } catch {  
  11.         return Json(false);  
  12.     }  
  13. }  

     

    Código JavaScript

  14. $(‘#btn-sendForm’).click(function() {  
  15.     var car = {  
  16.         prop1: document.getElementById(‘brand’).value(),  
  17.         prop2: document.getElementById(‘model’).value(), // …  
  18.     }  
  19.     $.ajax({  
  20.         url: ‘@Url.Action(«ACTION», «CONTROLLER»)’,  
  21.         type: ‘POST’,  
  22.         data: item,  
  23.         success: function(result) {  
  24.             var modelStatus = $(‘.model-status’);  
  25.             if (result) modelStatus.text(«Estado: Enviado con éxito»);  
  26.             else modelStatus.text(«Estado: Enviado con error. Compruebe que ha rellenado todos los campos»);  
  27.         },  
  28.         error: function(jqXHR, textStatus, error) {  
  29.             $(‘.model-status’).text(«Estado: Error inesperado»);  
  30.         }  
  31.     });  
  32. });  

     

    Solución NO óptima 2

    A priori podría parecer que la solución pasaría por dejar el formulario con el parámetro del tipo del modelo y crearnos un objeto en JavaScript con las propiedades necesarias e ir recogiendo los valores uno por uno de los campos del formulario para posteriormente enviarlos en la acción «Ajax» como el código que explico a continuación. Esta opción está muy cerca de la óptima porque recibimos el modelo en la acción y la validación se la delegamos al ModelState, pero aún sigue sin ser flexible ya que, si añadimos un nuevo campo al formulario, tendríamos que añadir esa nueva propiedad al objeto JavaScript para poder enviarlo y, como sabemos, esto siempre puede llevar a olvidos y/o errores.

    Acción del controlador

  33. [HttpPost] 
  34. public JsonResult Create(Car car) {  
  35.     try {  
  36.         if (!ModelState.IsValid) return Json(false);  
  37.         car.Status = «Solicitados»;  
  38.         return Json(true);  
  39.     } catch {  
  40.         return Json(false);  
  41.     }  
  42. }  

     

    Código JavaScript

  43. $(‘#btn-sendForm’).click(function() {  
  44.     var car = {  
  45.         prop1: document.getElementById(‘brand’).value(),  
  46.         prop2: document.getElementById(‘model’).value(), // …  
  47.     }  
  48.     $.ajax({  
  49.         url: ‘@Url.Action(«ACTION», «CONTROLLER»)’,  
  50.         type: ‘POST’,  
  51.         data: item,  
  52.         success: function(result) {  
  53.             var modelStatus = $(‘.model-status’);  
  54.             if (result) modelStatus.text(«Estado: Enviado con éxito»);  
  55.             else modelStatus.text(«Estado: Enviado con error. Compruebe que ha rellenado todos los campos»);  
  56.         },  
  57.         error: function(jqXHR, textStatus, error) {  
  58.             $(‘.model-status’).text(«Estado: Error inesperado»);  
  59.         }  
  60.     });  
  61. });  

     

    Solución ÓPTIMA

    Por último, vamos a ver la solución que a mi parecer es la óptima para estos casos. Lo que haremos, es tener la acción del controlador como en el caso anterior pero variaremos un poco el JavaScript para enviar todo el formulario de golpe. De esta forma, conseguimos que si tenemos que añadir un nuevo campo al formulario, no nos tengamos que preocupar cómo se envía ni cómo se recoge gracias al ModelBinding

    Acción del controlador

  62. [HttpPost] 
  63. public JsonResult Create(Car car) {  
  64.     try {  
  65.         if (!ModelState.IsValid) return Json(false);  
  66.         car.Status = «Solicitados»;  
  67.         return Json(true);  
  68.     } catch {  
  69.         return Json(false);  
  70.     }  
  71. }  

     

    Código JavaScript

  72. $(‘#btn-sendForm’).click(function() {  
  73.     var formContainer = $(‘#car-form’);  
  74.     $.ajax({  
  75.         url: ‘@Url.Action(«Create», «Car»)’,  
  76.         type: ‘POST’,  
  77.         cache: false,  
  78.         data: formContainer.serialize(),  
  79.         success: function(result) {  
  80.             var modelStatus = $(‘.model-status’);  
  81.             if (result) modelStatus.text(«Estado: Enviado con éxito»);  
  82.             else modelStatus.text(«Estado: Enviado con error. Compruebe que ha rellenado todos los campos»);  
  83.         },  
  84.         error: function(jqXHR, textStatus, error) {  
  85.             $(‘.model-status’).text(«Estado: Error inesperado»);  
  86.         }  
  87.     });  
  88. });  

     

    Como podéis observar, lo que haremos será serializar el formulario completo dentro de la propiedad «data» de la acción «Ajax» con lo que estaremos enviando todos los campos del formulario y, una vez en la acción del controlador, gracias al ModelBinding, el parámetro del modelo, en este caso «Car car» se rellenará de forma automática con todos los valores que procedan, los validamos con el ModelState y hemos acabado. Esto es totalmente flexible a cambios y, por lo tanto, no nos dará dolores de cabeza cuando tengamos que hacer modificaciones en el formulario.

     

    Resumen

    Siempre debemos intentar buscar la solución más óptima y flexible para nuestros desarrollos porque en el futuro, cuando tengamos que mantener el código u otro se encargue de esa tarea, nos alegraremos de haber perdido un poco de tiempo en ello.

    Podéis descargaros una solución simple de ejemplo desde mi GitHub