ASP.NET Preview 5 y escenarios Form Post

El pasado Jueves, el equipo de ASP.NET MVC publicó la Preview 5. Podéis descargarlo desde aquí. Esta Preview funciona tanto en .NET 3.5 y .NET 3.5 SP1. También se puede usar tanto en Visual Studio 2008 como en Visual Web Developer 2008 Express SP1 (que ahora soporta tanto proyectos de librería como proyectos web).

Como siempre, vienen un montón de características y refinamientos (basándose en las que ya traía la Preview 4). Podéis leer los detalles de estas nuevas caracteristicas aquí. En el post de hoy vamos a ver una de las grandes areas en las que se ha centrado esta release: Los escenarios Form Post. Podéis descargar la version completa de la aplicación que vamos a crear aquí.

Form Post básico con el patrón web MVC

Veamos un ejemplo simple de un Form Post - añadir un nuevo producto a una base de datos:

Esta página se devuelve cuando un usuario va a la url "/Products/Create" en nuestra aplicación. El formulario HTML de esta página tiene esta pinta:

Esto es HTML estándar. Tenemos dos text boxs <input type="text"/> en un elemento <form>. Luego tenemos un boton HTML al pie del formulario. Cuando lo pulsamos hacemos que el formulario haga un post al servidor. El formulario pone los valores en la URL indicada en el en el atributo "action" - en este caso "/Products/Save".

Con la Preview 4 de ASP.NET MVC habríamos implementado esto con una clase ProductsController como la siguiente que implementa dos métodos de acción: "Create" y "Save":

El método de acción "Create" es el responsable de devolver una vista html que muestre el formulario vacio. El método de accion "Save" se encarga de tratar el post del formulario cuando vuelve al servidor. El framework ASP.NET MVC mapea automáticamente los valores "ProductName" y "UnitPrice" a los parámetros del método que tienen el mismo nombre. La acción Save usa LINQ to SQL para crear un nuevo objeto Product, le asigna los valores proporcionados por el usuario, e intentan guardar el producto en la base de datos. Si el producto se inserta, se redirige al usuario a la url "/ProductsAdded" que mostrará el mensaje de éxito. Si hay algún error volvemos al html "Create" para que el usuario lo reintente.

Podemos implementar la vista "Create" de la siguiente forma para que funcione con la clase ProductsController. Fijáos que estamos usando los métodos helper Html.TextBox para generar los elementos <input type="text"/> (y obtener automáticamente su valor para la propiedad correcta del Product de nuestro modelo de objetos que se pasó a la vista):

Mejoras en los Post con la Preview 5

El código funciona con la versión anterior de la Preview 4, y sigue funcionando con esta Preview 5, sin embargo se añaden nuevas características que nos permitirán hacer esto de una forma mejor.

Entre estas nuevas características están:

  • La posibilidad de publicar una sóla url de acción y despacharla dependiendo del verbo HTTP
  • Model Binders que nos permiten pasar objetos más ricos como parámetros para que se creen desde los datos de entrada de un formulario  y que se pasen a métodos de acción.
  • Helper methods que permiten mapear valores de entrada de formularios a instancias de nuestro modelo de objetos con métodos de acción.
  • Mejora del soporte para validación de entradas y validación de errores (por ejemplo: iluminar campos erroneos y evitar que el usuario introduzca valores cuando se vuelve a mostrar el formulario.

Usaré esto como recordatorio para verlos más adelante.

Atributos [AcceptVerbs] y [ActionName]

En el ejemplo anterior implementamos la inserción de productos con dos métodos de acción: "Create" y "Save". La motivación de dividir esta implementación de esta manera es que hace el código de nuestro controlador más limpio y fácil de leer.

Lo malo de usar dos métodos de acción en este escenario, es que terminamos publicando dos urls desde nuestro sistio: "/Products/Create" y "/Products/Save". Esto puede llevar a problemas cuando necesitamos volver a mostrar el formulario cuando hay algún campo erróneo - ya que la url que se muestra cuando volvemos a mostrarlo es "/Products/Save" en lugar de "/Products/Create" (ya que "Save" era la url a la que se envió el post). Si un usuario añade esta url a los favoritos, o copia/pega la url y lo envía por email a un amigo, estarán guardando una url incorrecta- y verán un error cuando intenten visitarla. Publicar dos urls puede dar lugar a errores en algunos motores de búsqueda cuando está siendo rastreado e intentan saltarse los atributos de acción.

Una forma de evitar estos problemas es publicar una sola url "/Products/Create", y tener una lógica diferente cuando se haga un GET y un POST. Una aproximación para hacer esto con otros frameworks MVC es tener un enorme if/else en el método de acción:

Lo malo de esto es que hace que la lectura del código sea más complicada, y dificil de testear.

ASP.NET MVC Preview 5 nos ofrece otra forma de solventar esto. Podemos sobreescribir los métodos de acción, y usar el atributo [AcceptVerb] para que ASP.NET MVC filtre las peticiones. Por ejemplo, aquí tenéis dos métodos de acción Create - uno para lo escenarios GET y otro para los POST:

De esta manera ya no necesitamos el If/else, y nos permite estructurar de una manera más clara la lógica de la acción. También elimina la necesidad de hacer objetos mock para las peticiones que nos lleguen desde esos dos escenarios.

También podemos usar el atributo [ActionName] para conseguir que el nombre del método sea diferente en la url. Por ejemplo, si en vez de tener dos métodos sobreescritos en nuestro controlador queremos que el método POST se llame "Save", podemos aplicar este atributo de la siguiente manera:

Ahora tenemos el mismo controlador que al principio. La diferencia es que sólo estamos publicando una sola URL (/Products/Create) y dependiendo del tipo de peticion se hará una u otra (evitando el problema de los favoritos del navegador y de los motores de búsqueda que enumeramos ántes).

Model Binders

Ahora tenemos un eejemplo en el que la firma del método de acción de nuestro controlador  toma un string y un decimal como argumento. El método de acción crea un nuevo producto, le asigna esos valores de entrada e intenta guardarlos en la base de datos:

Una de las nuevas capacidades de esta Preview 5 es que consigue un soporte más limpio para los "Model Binder". Los Model Binders permiten una forma de serializar tipos complejos desde entradas HTTP y pasarlos como argumentos a métodos de acción. También nos permiten controlar excepciones, y hacer más sencillo mostrar los errores cuando ocurren (sin tener que pedirle al usuario que reescriba todos los datos otra vez - lo veremos más adelante en este post).

Por ejemplo, usando este soporte podemos refactorizar el método de acción anterior para que reciba un objeto de tipo Product:

Esto hace el código más claro. Nos permite evitar parsear datos en todos los métodos de acción (permitiendonos seguir el principio DRY: "Don't repeat yourself" (no te repitas).

Registrar Model Binders.

Los Model Binders en ASP.NET MVC son clases que implementan la interfaz IModelBinder, y pueden usarse para facilitar los enlaces de tipos a argumentos. Un model binder puede escribirse para que funcione con un tipo concreto, o puede usarse para que administre varios tipos. La interfaz IModelBinder nos permite hacer test unitarios a cada binder independientemente del servidor web o de cualquier implementación de los controladores.

Se pueden registrar a cuatro niveles diferentes en una aplicación ASP.NET MVC, lo que nos proporciona una gran flexibilidad en cómo podemos usarlo:

1. ASP.NET MVC busca primero si hay algún model binder declarado como parámetro en un método de acción. Por ejemplo, podemos indicarle que queremos que use un hipotético binder "Bind" marcando el parámetro product de la siguiente manera (fijáos que le estamos indicando sólo dos propiedades que deben enlazarse usando un parámetro en el atributo):

Nota: La Preview 5 no tiene un atributo Bind como el anterior (aunque estamos considerando añadirlo como una característica en el futuro. Aunque toda la infraestructura necesaria para implementar un atributo [Bind] está implementada en la preview 5. El proyecto open source MVCContrib también tiene un atributo DataBind como el anterior para que lo uséis hoy.

2. Si no hay ningún atributo de este tipo, ASP.NET MVC busca un binder registrado como atributo en el tipo del parámetro que se está pasando al método de acción. Por ejemplo, podemos registrar el binder "ProductBinder" para nuestro objeto "Product" de LINQ to SQL añadiendo el siguiente código a la clase parcial Product:

3. ASP.NET MVC también permite registrar estos binders al inicio de la aplicación usando la colección ModelBinders.Binders. Es muy util si queremos usar un tipo escrito por un tercero (que no podemos decorar) o si no queremos añadir estas decoraciones en nuestro modelo de objetos directamente. El código siguiente muestra cómo registrar dos binders a dos tipos en concreto cuando se inicia la aplicación en el global.asax:

4. También podemos usar la propiedad ModelBinders.DefaultBinder para registrar un binder por defecto que será usado cuando no se encuentre un binder adecuado. En el assembly MVCFutures (que está refereneciado por defecto en las builds de las previews) está la implementación ComplexModelBinder que usa reflexión para asignar las propiedades basándose en los pares nombre/valor de los post que se hagan. Para registrarlo para todos los tipos complejos de los métodos de accción de un controlador usaremos este código:

Nota: el equipo de MVC está planeando trucar la interfaz IModelBinder para la próxima preview (han descubierto unos cuantos escenarios en los que hacen falta algunos cambios). Así que si creais un binder personalizado con la preview5 tendréis que hacer algunos cambios en las siguientes versiones (seguramente nada complicado - pero sabemos que vamos a cambiar algo).

Métodos UpdateModel y TryUpdateModel

El soporte ModelBinder nos permite un montón de escenarios en los que podremos instanciar nuevos objetos y pasarlos como argumentos a métodos de acción. También habrá escenarios en los que querremos ser capaces de enlazar valores de entrada a instancias de objetos que obtenemos/creamos dentro de un método de acción. Por ejemplo, cuando habilitamos la edición de productos en una base de datos, querremos usar un ORM para obtener instancias de objetos existentes en la base de datos, enlazarlos con un objeto y guardarlo en la base de datos.

La preview 5 añade dos nuevos métodos a las clases controladoras para ayudarnos a conseguir esto - UpdateModel() y TryUpdateModel(). Ambos nos permiten pasar una instancia de objeto como primer argumento, y como segundo parámetro le pasamos en una lista blanca de seguridad, las propiedades que queremos actualizar a partir de los valores que vienen por una petición post. Por ejemplo, aquí estamos obteniendo un objeto Producto con LINQ to SQL, y usamos el método UpdateModel() para actualizar el nombre y el precio del producto:

Los métodos UpdateModel intentará actualizar todas las propiedades de la lista (aunque haya un error). Si se encuentra con un error en una propiedad (por ejemplo: si introducimos valores erroneos en la propiedad UnitPrice que es de tipo Decimal), guardará el objeto exception que se lanzó así como el valor original en una nueva colección "ModelState" que se ha añadido a esta Preview5.  Veremos esta nueva colección un poco más adelante - pero para adelantar, nos permite una forma sencilla de volver a mostrar formularios con los valores que el usuario introdujo y le pedirá que los cambie.

Tras intentar actualizar las propiedades, el método UpdateModel lanzará una excepción si alguno de ellos falló. El método TryUpdateModel funciona de la misma manera - pero en lugar de lanzar una excepción devuelve un booleano cierto/falseo que indica si ha ocurrido un erro. De manera que usaremos el que más nos convenga.

Ejemplo de edición de producto:

Para ver un ejemplo del uso del método UpdateModel, implementaremos un formulario de edición muy básico. Usaremos la url /Product/Edit/{ProductId} para indicar el producto que queremos editar. Por ejemplo, en la url /Products/Edit4 - estamos editando el producto cuyo ProductId es 4:

Podremos cambiar el nombre o el precio, y hacer clic en el botón Save. Cuando ocurre el post actualizaremos la base de datos y mostraremos el mensaje de "Product Updated!" si todo fué bien:

Podemos implementar esta funcionalidad con los dos métodos de acción. Fijáos cómo estamos usando el atributo [AcceptVerbs] para diferenciar la acción de edición:

El método de acción POST usa LINQ to SQL para crear una instancia del objeto product que estamos editando de la base de datos, luego usa UPdateModel para intentar actualizar el nombre y el precio usando los parámetros que se han pasado por post. Después llama a SubmitChanges() en el datacontext de LINQ to SQL para guardarlo. Si terminó con exito, mostramos un mensaje en la colección TempData y redirigimos al usuario al método de acción GET con una redirección del lado cliente (lo que hará que el nuevo producto se muestre - junto al mensaje TempData indicando que no hubo ningún problema. Si hubiese ocurrido un error, tanto en los valores de Post o mientras se actualizaba en la base de datos, se lanzará una excepción, será recogida en el catch - y mostraemos el formulario de vista al usuario solicitando que lo arregle.

Os estaréis preguntadno - Que pasa con esta redirección cuando va todo bien?¿Porqué no volvemos a mostrar el formulario otra vez y mostramos el mensaje de éxito? La razón de la redirección cliente es para asegurarnos de que si el usuario le da al botón de refrescar despues de que todo vaya bien, es para que no vuelva a hacer otra petición y ocurra esto:

Haciendo la redirección a la versión GET del método de acción nos aseguramos que cuando el usuario le de a refrescar simplemente recargará la página y no hará un postback. A esto se le conoce como el patrón PRG Post/Redirect/Get. Tim Barcz tiene un artículo en el que habla más sobre esto con ASP.NET MVC.

Los siguientes métodos de acción también los necesitamos para implementar la edición y la actualización de un producto. Aquí tenéis la vista "Edit" que va con el controlador:

Truco útil: Ántes, una vez que empezamos a añadir parámetros a las URLs (por ejemplo: /Products/Edit/4) teníamos que escribir código en la vista para actualizar el atributo acción para incluir los parámetros en el post de la URL. La preview 5 incluye el método Html.Form() que hace esto más fácil. Tiene muchas sobrecargas que nos permiten especificar un montón de diferentes parámetros. Se ha añadido una nueva sobrecarga de este método sin parametros que devolvera la misma URL que la petición actual.

Por ejemplo, si la Url que llega al controlador se renderizó en la vista como "/Products/Edit/5", llamando a Html.Form() devolverá automáticamente <form action="/Products/Edit/5" method="post">. Si se renderizó como "/Products/Edit/55", la llamada devolverá <form action="/Products/Edit/55" method="post">. Esto nos brinda una forma de evitar tener que escribir código para crear la url o indicar los parámetros.

Test unitarios y UpdateModel

En esta preview 5, los métodos UpdateModel funcionan trabaja con la colección de objetos de petición del post del formulario para obtener los valores. Con lo que conseguiemos que para testear métodos de acción post no tengamos que crear objetos mock para las peticiones en nuestros test.

Con la próxima versión también sobrecargaremos el método UpdateModel para que permita pasarle una colección de valores propios. Por ejemplo, podremos usar el tipo FormCollection (que tiene un ModelBuilder que se crea con todos los valores que le pasemos al formulario por post) y se lo pasamos al método UpdateModel de la siguiente manera:

De esta manera, podemos crear test unitario en escenaros post sin tener que hacer ningún objeto mock. Aquí tenéis un ejemplo de un test unitario que podríamos escribir en este tipo de escenarios para simular una actualización correcta con los valores y redirecciones a la versión GET de nuestro método de acción. Fijaos que no tenemos que hacer mock de nada (ni siquiera tenemos que usar un helper method) para probar toda la funcionalidad de nuestro controlador:

Gestionando escenarios de errores - Mostrar formularios con mensajes de error

Una de las cosas más importantes en escenarios POST son las condiciones de error. Esos casos abarcan desde los que un usuario introduce valores errores (por ejemplo: una cadena en lugar de un número decimal para un precio), hasta los casos en los que aunque el formato sea correcto, las reglas de negocio de la aplicación no admiten esos valores para la creación/modificacion (por ejemplo: crear una nueva orden para un producto discontinuado).

Si un usuario se equivoca al rellenar un campo, el formulario debe de mostrar el error de alguna manera, con información suficiente para que se corrija. El formulario debe guardar los valores que el usuario introdujo la primera vez - para que no tengan que volver a escribirlos. Este proceso debe repetirse las veces que sean necesarias hasta que todo esté correcto.

Tratamiento básico de errores y validaciones con ASP.NET MVC

En nuestro ejemplo de edición de productos no hemos hecho mucho caso a esto de los errores ni siquiera en el controlador ni en la vista. En la acción post Edit simplemente ponemos un try/catch alrededor del UpdateModel(), y en la llamada a SubmitChanges(). Si ocurre un error, el controlador guarda el mensaje de salida en la colección TempData, y devuelve la vista:

En versiones anteriores de ASP.NET MVC el código anterior no habría bastado para tener una experiencia de usuario amigable (ya que no realza el problema, ni advierte al usuario de si ocurrió un error).

Sin embargo, en la preview 5 que hemos arregaldo esto con el código anterior. Concretamente, veremos que en la vista de edición se repinta debido a un error de entrada y destaca los campos con problemas:

¿Cómo se realza el textbox del precio sólo en rojo y cómo sabe el valor que se le pasó la primera vez?

Se ha añadido una nueva colección "ModelState" que se pasa como parte de los "ViewData" que se envían desde el controlador a la vista cuando se renderiza. Esta colección permite a los Controladores indicar que existe un error en un objeto de nuestro modelo o en una propiedad que se ha pasado a la vista, y permite mostrar un mensaje amigable y específico que lo describe, así como el valor que se introdujo.

Los desarrolladores poueden escribir código para añadir elementos a esta colección en las acciones de los controladores. Los ModelBinders de ASP.NET MVC y los métodos UpdateModel() también obtienen esta colección por defecto cuando se encuentrar con errores. Como estamos usando el método UpdateModel() en la acción de editar, cuando falla al intentar mapear el precio de la unidad "gfgff23.02" (que es de tipo decimal en la propiedad Product.UnitPrice) se añade automáticamente una entrada a la colección ModelState.

Los métodos de ayuda Html dentro de la vista comprobarán automáticamente la colección ModelState para renderizar la salida. Si ocurre un error en algún elemento, renderizarán el valor que se pasó y la CSS de dicho elemento. Por ejemplo, en la vista anterior de edición estamos usando el método Html.TextBox() para renderizar el precio por unidad del objeto producto:

Cuando se renderizó la vista el método Html.TextBox comprobó la colección ViewData.ModelState para ver si había algún problema con la propiedad UnitPrice, y cuando lo vió renderizó la entrada primera ("gfgff23.02")  y añadió una clase css al elemento <input type="textbox"/>:

Podemos personalizar la apariencia de este error en las clases css como queramos. La css por defecto es la siguiente:

Añadiendo mensajes de validación adicionales.

Los métodos de ayuda HTML que vienen integradon nos permiten una identificación visual básica en los campos con problemas. Vamos a añadir mensajes más descriptivos sobre los errores. Para esto vamos a usar el método Html.ValidationMessage(). Este método mostrará el mensaje de error de la colección ModelState que está asociado a la propiedad.

Por ejemplo: Podemos actualizar la vista para que use el método Html.ValidationMessage() de la siguiente manera:

Ahora, cuando se renderize la página con un error, se mostrará un mensaje de error al lado de los campos con problemas:

Hay una sobrecarga del método Html.ValidationMessage() que toma un segundo parámetro que permite especificar un texto alternativo:

Un caso muy típico es mostrar un * al lado del campo con problemas, y añadir el nuevo método Html.ValidationSummary() al principio de la página para que liste todos los mensajes de error:

Cuando se renderice el método Html.ValidationSummary() lo hará como una lista <ul><li></ul> con todos los mensajes de error de la colección ModelState, y un * y un borde rojo indicará cada elemento con un problema:

Fijáos que no hemos tenido que cambiar nada de la clase ProductsController para todo esto.

Soporte de validación de relgas de negocio

Este tipo de ayuda en estos escenarios es muy útil, pero rara vez son suficientes en la mayoría de aplicaciones. Pero en casi todos los escenarios querremos ser capaces de reforzar las reglas de negocio, y que la integración de estas en la interfaz de usuario sea limpia.

ASP.NET MVC soporta cualquier abstracción en la capa de datos (tanto con ORMs o sin ellos), y nos permite estructurar el modelo de dominio, las reglas y restricciones como queramos. Capacidades como los Model Binders, el UpdateMOdel, y todos los mensajes de error en las vistas están diseñados para que podamos conseguir la capa de datos que queramos (con LINQ to SQL, LINQ to Entities, NHibernate, SubSonic, CSLA.NET, LLBLGen Pro, WilsonORMapper, DataSets, ActiveRecord, y muchos otros).

Añadir reglas de negocio a una entidad LINQ to SQL

En el ejemplo anterior hemos usado LINQ to SQL para definir la entidad Product y para realizar los accesos a datos. El único nivel de dominio que estamos usando son las que LINQ to SQL deriba de los metadatos del servidor SQL Server (nulls, tipos de datos, longitudes, etc). Con esto nos basta en escenarios como el de arriba (en el que intentamos asignar valores de entrada a un Decimal). Sin embargo, no son suficientes para establecer unas reglas de negocio que sean fácilmente declaradas con metadatos de SQL. Por ejemplo: deshabilitar la creación de órdenes de un producto si está discontinuado, o evitar que un producto se venda por menos de lo que nos costó, etc. Para escenarios de este tipo necesitamos añadir código a nuestro modelo para expresar e integrar estas reglas de negocio.

El sitio equivocado en el que añadir esta lógica es en la capa de presentación. Añadirlas ahí no es correcto por muchas razones. Entre otras, acabaremos duplicando código - copiándolas de un lado a otro. Además de que consume más tiempo, es una forma de llenar el código de bugs cuando cambiamos las reglas de negocio en una parte y se nos olvide cambiarlo en otra.

El mejor lugar es siempre en el nivel de dominio. De esta manera podremos usar y aplicar siempre las mismas reglas sin importar de qué parte de la interfaz de usuario vengamos. Los cambios en esas reglas harán efecto en todas partes, sin tener que duplicar código.

Hay muchos patrones que podemos usar para integrar reglas de negocio ricas al modelo de objetos de Product que hemos usado antes: podríamos definir reglas en el objeto, o fuera. Podemos usar reglas declaratibas, un motor de reglas reusables o código imperativo. La clave es que ASP.NET MVC nos permite hacer lo que queramos (no hay muchas características que te obliguen a hacerlo siempre de la misma manera - sin embargo tienes la posibilidad de hacerlo cómo queramos, y MVC es lo suficientemente extensible para integrar la mayoría de las cosas).

Para este post vamos a usar unas reglas muy sencillas. Primero, vamos a definir una clase "RuleViolation" que usaremos para capturar información sobre una regla de negocio que está siendo violada en nuestro modelo. Esta clase expondrá un string ErrorMessage sobre el error, y dos propiedades Nombre y Valor asociado al mensaje de error:

(nota: Para simplificar sólo voy a guardar una propiedad -  en aplicaciones más complejas esto puede ser una lista de varias propiedades).

Definiremos la interfaz IRuleEntity con un sólo método - GetRuleViolations() - que devolverá la lista de todas las violaciones de reglas de esa entidad:

Ahora hacemos que la clase Product implemente esta interfaz. Para mantener simple el ejemplo estoy embebiendo la definición de la regla y la evaluación de la lógica dentro del método. Hay mejores patrones para crear reglas reusables, y para crear reglas más complejas. Si este ejemplo crece refactorizaré este método para que las reglas y su evaluacion estén definidas en alguna parte, pero por ahora lo mantendremos aquí:

Nuestra aplicación puede preguntar al producto (o cualquier otro que implemente IRuleEntity) para que se autovalide, y obtener objetos RuleViolation para presentarlo a la interfaz de usuario y guiar a este para que resuelva el problema. De esta forma también podemos crear test para nuestras reglas de negocio independientemente de la interfaz de usuario.

Para este ejemplo vamos a reforzar el objeto Product para que no se guarde en la base de datos en estado inválido (es decir, hay que corregir todas las RuleViolations ántes de poder guardar el objeto en la base de datos). Podemos hacer esto con LINQ to SQL añadiendo el método parcial OnValidate a la clase parcial Product. Este método se llamará automáticamente por LINQ to SQL cuando se vayan a persistir los datos. En el siguiente código estamos llamando a GetRuleViolations que añadimos antes, y estamos lanzando una excepción si hay errores sin resolver. Esto abortará la transacción y evitará que se actualize la base de datos:

Y ahora además tener un método de ayuda que nos permite obtener las violaciones de reglas de un producto, forzaremos esas reglas para que se corrigan ántes de que se actualice la base de datos.

Integrar las reglas anteriores en la interfaz de usuario de ASP.NET MVC

Una vez que tenemos implementadas nuestras reglas de negocio, y hemos expuesto las RuleViolations, es relatívamente fácil integrarlas en nuestro ejemplo

Como hemos añadido el método parcial OnValidate a la clase producto, cuando llamemos a northwind.SubmitChanges() lanzará una excepción si hay reglas que no se cumplen. Esta excepción abortará la transacción de la base de datos, y será capturada en nuestro catch:

La línea extra que hemos añadido es un poco de lógica para llamar a UpdateModelStateWithViolations() que definimos más abajo. Este método obtiene la lista de las violaciones de reglas de la entidad, y actualiza la coleccción ModelState con errores apropiados (incluidos las referencias a las propiedades de nuestro objeto que las causó:

Una vez hecho esto, arrancamos la aplicación. Ahora, además de ver los mensajes de error, ASP.NET MVC mostrará también las reglas de negocio que no se cumplen.

Por ejemplo, podemos decir que el presio debe ser menor que 1 dolar, e intentar poner el nivel Reorder a -1 (ambos valores son válidos desde el punto de vista de formato - pero no para nuestras reglas de negocio). Cuando lo hacemos y le damos a salvar, veremos los siguientes errores en la lista del método Html.ValidationSummary(), y se realzarán los textboxes correspondientes:

Nuestras reglas de negocio pueden afectar a varias propiedades de Product. Por ejemplo, os habréis fijado de que hemos añadido una regla que dice que el nivel de reordenacion no puede ser mayor que cero si el producto está discontinuado:

Los únicos cambios que hemos hecho es que el template de la vista View en todo el proceso de las reglas de negicio ha sido añadir dos propiedades de Product (Reorder y Discontinued) al archivo:

Ahora podemos añadir todas las reglas que queramos a nuestra entidad Product, y no tenemos que actualizar la vista Edit ni la clase ProductsController para que la interfaz de usuario las soporte.

También podemos testear nuestro modelo y las reglas de negocio de manera aislada sin la necesidad de un controlador o una vista. Podemos testear las rutas URL sin un controlador, vista o modelos. Y podemos testear los controladores sin necesidad de vistas. Todos los escenarios que hemos visto en este post soportan test unitarios sin necesidad de ningún objeto mock. El resultado final es que las aplicaciones son fácilmente testeables, y nos permiten un workflow de desarrollo TDD muy sencillo.

Resumen

Este post nos ha dado un vistazo sobre los escenarios con acciones post de formularios con ASP.NET MVC Preview 5. Después de leerlo tendremos claro cómo trabajar con entradas de datos en formularios con el modelo MVC. Podéis descargaros una versión completa en C# de la aplicación del ejemplo aquí. Postearé una versión en VB en esta semana (son las 4:30 de la mañana mientras estoy escribiendo esto y tengo que cojer un avión en unas horas y no he hecho todavía la maleta).

Importante: Si no te gusta el modelo MVC o no lo encuentras natural para tu forma de desarrollo, no tienes porqué usarlo. Es totalmente opcional - y no reemplaza a ningún modelo de WebForms existente. Tanto WebForms como MVC serán completamente soportados y ampliados (la próxima release de ASP.NET WebForms añadirá nuevas características en el rutado de URLs, mejor soporte HTML para markup/clientes/CSS, y más). Así que despues de leer este post estaréis pensando "hmm- no me parece algo muy natural", entonces no te preocupes, y no creas que debes usarlo (no debes).

En el próximo post sobre MVC veremos cómo integrar AJAX en nuestras aplicaciones ASP.NET MVC

Espero que sirva.

Scott.

Traducido por: Juan María Laó Ramos.

Artículo original.

Deja un comentario