Validaciones en ASP.NET MVC

  • Por
  • .NET
Vemos qué son las validaciones en ASP .NET MVC y cómo utilizarlas de una forma efectiva.
En los dos artículos anteriores hemos visto como enviar datos a un controlador usando POST y como generar los formularios usando los helpers. Introdujimos también el concepto de Model Binding (como ASP.NET MVC era capaz de construir un objeto que recibía el controlador a partir de los datos de la petición) y vimos como al usar los helpers obteníamos gratuitamente algunas validaciones, p.ej. si declarábamos un campo como int, ASP.NET MVC comprobaba que no entrásemos caracteres alfanuméricos.

De hecho lo que vemos automáticamente al usar los helpers no son validaciones, son errores. Si introducimos una cadena alfanumérica en un campo que se ha declarado como int, cuando el Model Binder debe enlazar los datos genera un error. Los errores que el Model Binder se encuentra al intentar enlazar los valores de la request con las propiedades del viewmodel son guardados en el ModelState y pueden ser consultados desde el propio controlador y también desde la vista. De hecho, como vimos en el artículo anterior, los helpers consultan el ModelState para usar una clase CSS específica en caso de que haya algún error asociado con el campo.

Intentar convertir una cadena alfanumérica en un int es pues un error, pero hay muchos más casos en los que nos puede interesar decirle al usuario que los datos entrados son incorrectos: campos obligatorios, cadenas con un determinado formato (p.ej. un email) o dos campos que deben tener el mismo valor (p.ej. contraseña y comprobar contraseña). Son esos casos cuando nos referimos a validaciones y es lo que vamos a tratar en nuestro artículo. Pero aunque nosotros diferenciemos entre errores y validaciones ASP.NET MVC no lo hace: ambos son tratados igual, es decir ambos son guardados en el ModelState. Pero antes de profundizar más, veamos exactamente que es el ModelState.

ModelState o estado del modelo

El ModelState es un objeto, gestionado por el framework de ASP.NET MVC que indica cual es el estado del modelo. En este contexto por modelo entendemos el objeto que recibe un controlador (lo que generalmente llamamos view model) y por estado entendemos si es correcto o no. Correcto significa que los datos de la petición eran válidos y que el Model Binder ha podido crear un objeto y rellenarlo. Incorrecto significa que había algún dato de la petición inválido, ya sea por algún error (una cadena alfanumérica se ha intentado asignar a un int) o bien por alguna validación fallida (una cadena declarada como email no tenía el formato correcto).

El ModelState es básicamente un diccionario donde:

  1. Las claves son los nombres de las propiedades de nuestro modelo (realmente son los nombres que usa el Model Binder para enlazar las propiedades pero podemos asumir que coinciden).
  2. Por cada clave hay el valor que el Model Binder ha asignado a esa clave y lo que más nos importa: una colección de los errores vinculados a esa clave si los hubiese. Y cuando aquí digo errores me refiero tanto a errores como a validaciones fallidas.

Figura 1: ModelState con un error

La Figura 1 muestra la composición del ModelState. La propiedad Keys contiene las claves (en este caso el viewmodel que estábamos usando era una clase con una propiedad cadena Nombre y un int llamado Edad) y la propiedad Values donde podemos ver, por cada clave, el valor que le ha sido asignado (Value) y la colección de errores (Errors).

El ModelState contiene una propiedad llamada IsValid que nos dice si el modelo es correcto, es decir si no hay errores. Esa propiedad suele usarse de la siguiente forma:

[HttpPost]
      public ActionResult Index(DemoModel data)
      {

           if (!ModelState.IsValid)
           {
                 return View(data);
            }
           else
           {
                 // Codigo normal
           return View("ok");
            }

     }

Si el ModelState no es válido significa que alguna entrada del usuario no es correcta, por lo tanto devolvemos de nuevo la vista que contiene el formulario con los datos. Como vimos en el artículo anterior si usamos los helpers para crear el formulario, esos mostrarán los errores (en color rojo con la CSS por defecto). Si el ModelState es válido eso significa que las entradas del usuario son correctas por lo que procedemos a realizar la acción que querramos.

Añadir nuestras validaciones

Bueno, hemos visto el mecanismo que usa el framework de MVC para indicarnos que hay algún error, el ModelState. Ahora vamos a ver cómo podemos añadir nuestras validaciones. Aunque ASP.NET MVC es muy flexible en este punto, vamos a ver la forma más común de hacerlo: usando DataAnnotations. En artículos posteriores veremos otras formas.

Para añadir validaciones usando DataAnnotations simplemente debemos decorar la propiedad que deseemos con alguno de los atributos (algunos se encuentran en el namespace System.ComponentModel.DataAnnotations y otros en System.Web.Mvc). P.ej. si tenemos el siguiente viewmodel:

public class DemoModel
      {
            public string Nombre { get; set; }
           public int Edad { get; set; }
     }

Si queremos que el Nombre sea obligatorio podemos decorarlo con [Required]:

public class DemoModel
      {
            [Required]
           public string Nombre { get; set; }
            public int Edad { get; set; }
     }

¡Y listos! Solo con añadir el atributo Required, el framework sabe que el Nombre es requerido y si el usuario no lo entra se mostrará un error:


Figura 2: El error generado por [Required]

Si comparas la Figura 2 con las capturas de pantalla del artículo anterior donde también se mostraban campos erróneos, verás una pequeña diferencia: no solo se muestra el campo de rojo, sino que también hay un mensaje de error. ¿Quién añade ese mensaje de error? Pues un helper del que todavía no habíamos hablado: Html.ValidationMessageFor. Su uso es como lo de los helpers para generar formularios: con una expresión lambda le indicamos la propiedad para la cual queremos mostrar sus mensajes de error (si los hubiese). Ese es el código completo de la vista que estamos usando:

@using (Html.BeginForm()) {
      @Html.ValidationSummary(true)
     <fieldset>
            <legend>DemoModel</legend>

            <div class="editor-label">
                  @Html.LabelFor(model => model.Nombre)
            </div>
           <div class="editor-field">
                 @Html.EditorFor(model => model.Nombre)
           @Html.ValidationMessageFor(model => model.Nombre)
           </div>

            <div class="editor-label">
                  @Html.LabelFor(model => model.Edad)
            </div>
           <div class="editor-field">
                 @Html.EditorFor(model => model.Edad)
                  @Html.ValidationMessageFor(model => model.Edad)
            </div>

            <p>
                 <input type="submit" value="Create" />
           </p>
      </fieldset>
}

Otros atributos para validar

Hay varios atributos para distintas validaciones y todos se usan igual: decorando las propiedades. Si tu aplicación exige que sus usuarios tengan entre 18 y 65 años, se puede usar Range:

public class DemoModel
      {
           [Required]
           public string Nombre { get; set; }
           [Range(18, 65)]
           public int Edad { get; set; }
     }

Más atributos que existen:

  • StringLength: Para limitar el número de caracteres de un campo texto (p.ej. el password debe tener entre 6 y 15 caracteres).
  • Compare: Para que dos campos tengan el mismo valor (p.ej. password y repetir password)
  • RegularExpression: Para validar contra una expresión regular
Por supuesto existe la posibilidad de crearte tus propios atributos para validaciones propias… ¡pero es eso es algo que dejamos para un artículo posterior!

Personalizar las validaciones

Si ves la Figura 2 probablemente te preguntes si es posible modificar el color que se usa para mostrar los errores y el mensaje que se muestra. La respuesta a ambas preguntas es sí. Para modificar el color que se usa para mostrar los errores basta con modificar la css. Los helpers usan la clase input-validation-error para indicar que el campo de formulario está mostrando un error, así que su aspecto depende de cómo tengamos definida esa clase en la CSS. Por otro lado el mensaje que vemos es el que genera el framework por defecto, pero podemos modificarlo por uno nuestro usando la propiedad ErrorMessage que tienen todos los atributos de validación:

public class DemoModel
      {
           [Required(ErrorMessage = "Nada de anónimos. ¡Aquí todo el mundo tiene un nombre!")]
           public string Nombre { get; set; }
            [Range(18,65, ErrorMessage = "Solo mayores de edad no jubilados")]
           public int Edad { get; set; }
     }
IMAGEN
La Figura 3 muestra el resultado con esos mensajes de error:


Figura 3: Errores con mensajes personalizados

Sumario de validaciones fallidas

Hemos visto como el helper Html.ValidationMessageFor nos permite mostrar los mensajes de error vinculados a una propiedad determinada. Pero a veces nos puede interesar mostrar un resumen con todos los mensajes de error, algo como:


Figura 4: Sumario de validaciones fallidas

Pues bien, como no podía ser de otro modo existe un helper específico para ello, llamado Html.ValidationSummary. Este helper puede generar una lista (<li>) de elementos no ordenados (<ul>) con todas las validaciones que hayan fallado. Su uso es tan simple como añadir dicho helper en el lugar donde queramos que aparezca dicha lista. Además admite varios parámetros para personalizarlo uno de los cuales permite añadir un título que se mostrará antes de la lista:

@Html.ValidationSummary(true,"Hay varios errores:")

La cadena "Hay varios errores:" se mostrará antes de mostrar la lista. El primer parámetro por su parte indica si la lista debe mostrar todos los errores (true) o solo aquellos que no estén vinculados a ninguna propiedad (false), es decir que sean globales a todo el viewmodel (en efecto, es posible que haya errores en el ModelState que no estén asociados a ninguna propiedad en concreto).

Validación en cliente

Si usamos los atributos estándar (no creamos atributos propios) ya tenemos la validación en cliente automática. Es decir al mismo tiempo que el usuario vaya tecleando los valores o cambie el foco ya ser irán mostrando los distintos errores que haya. Si hay errores no podrá enviar el formulario. Por supuesto la validación en cliente no tiene nada que ver con la seguridad, es un tema de usabilidad (darle feedback al usuario de forma más rápida) así que el uso de validación en cliente no inhibe de realizarla en servidor. ASP.NET MVC la realiza siempre en servidor (y nosotros por nuestra parte debemos comprobar siempre el valor de la propiedad IsValid del ModelState).

Si por alguna razón se desea desactivar la validación en cliente en alguna vista, basta con llamar al método Html.EnableClientValidation con el parámetro a false:

@{ Html.EnableClientValidation(false); }
@using (Html.BeginForm())
      {
            // Codigo del form
     }

Si creas atributos propios para validaciones personalizadas entontes es responsabilidad tuya asegurarte de que sean compatibles para validar en cliente (tranquilo, veremos en artículos posteriores como hacerlo), pero los que vienen, lo incluyen de serie.

Añadiendo errores propios al ModelState

Para terminar este artículo me gustaría mostrar cómo podemos añadir errores propios en el ModelState. Esto puede ser útil en aquellos casos en que el código de validación no pueda ser incorporado en un atributo (porque no dependa únicamente de los valores introducidos por el usuario sino que también dependa de factores externos). El ModelStatetiene un método llamado AddModelError que sirve exactamente para esto:

[HttpPost]
      public ActionResult Index(DemoModel data)
      {

            if (ModelState.IsValid)
            {
                  if (Manager.ExistePersona(data))
                  {
                        ModelState.AddModelError("", "Ya existe una persona con este nombre");
                  }
            }

            if (!ModelState.IsValid)
           {
                  return View(data);
           }
            else
           {
                 // Codigo normal
                 return View("ok");
            }

}

En esta acción, si no hay errores en el modelo (es decir los datos introducidos por el usuario son correctos) el controlador comprueba si ya existe una persona con esos datos (usando una clase inventada Manager), y si este es el caso añade un error en el ModelState. En este momento ModelState.IsValid deja de ser true (puesto que ahora hay un error). ModelState.AddModelError tiene dos parámetros:

  1. A que propiedad se asigna el error. Si vale "" no se asigna a ninguna propiedad (se entiende que es un error global que afecta a todo el viewmodel).
  2. El mensaje de error asociado a dicho error.
Como podemos observar el que exista el error de "Ya existe una persona con este nombre" es algo que depende de los datos introducidos pero sobretodo del estado del sistema (que exista o no ya una persona con este nombre). Estos tipos de errores son los que suelen tratarse usando AddModelError.

Bueno, en este artículo nos hemos introducido en el modelo de validaciones de ASP.NET MVC. Hemos visto que es el ModelState, como usar DataAnnotations y como añadir nuestros propios errores en el ModelState. Nos han quedado varios aspectos que iremos tratando en futuros artículos como crear nuestros propios atributos de validación y ver otros mecanismos de validación que no sea usando atributos.