> Manuales > Manual del framework ASP.NET MVC

Como crear validaciones propias en ASP.NET MVC tanto en servidor como en cliente.

En el artículo anterior vimos cómo usar Data Annotations para crear validaciones y como usar los helpers para mostrar los mensajes de errores de las validaciones fallidas. Pero nos quedó en el tintero ver cómo podemos crear nuestras propias validaciones, si las que vienen de serie no nos sirven. Eso es lo que vamos a ver en este artículo.

Atributos de validación propios

La manera en que podemos extender Data Annotations para validaciones personalizadas es creando un atributo de validación propio, que luego podremos aplicar a las propiedades de nuestro viewmodel que queramos validar.

Para crear un atributo de validación propio basta con derivar de la clase ValidationAttribute y por lo general redefinir un solo método: IsValid. Dicho método recibe un object con el valor a validar y debe devolver un booleano indicando si la validación ha sido correcta o no:

[AttributeUsage(AttributeTargets.Property)]
    public class NumeroParAttribute : ValidationAttribute
    {
      public override bool IsValid(object value)
      {
         try
         {
            var number = Convert.ToInt32(value);
            return number % 2 == 0;
         }
         catch (Exception)
         {
            return base.IsValid(value);
         }
      }
   }

El atributo AtributeUsage se usa para indicarle a .NET donde es válido aplicar este atributo (esto es siempre que se creen atributos, ya sean para validaciones o para cualquier otra tarea). Aquí estamos indicando que este atributo se aplica a propiedades, no a métodos o a parámetros.

Una vez creado el atributo su uso es como cualquiera de los que vienen de serie: basta con aplicarlo a las propiedades que deseemos validar.

public class DemoModel
    {
      [Range(1, 100, ErrorMessage = "Positivo menor que 100")]
      [NumeroPar(ErrorMessage = "El número debe ser par.")]
      public int ValorPar { get; set; }
   }

Al derivar de la clase ValidationAttribute ya obtenemos la propiedad ErrorMessage que vimos en el artículo anterior y que nos permite especificar un mensaje de error a mostrar en caso de que la validación sea fallida. También podemos observar como a pesar de que la clase que hemos creado se llama NumeroParAttribute para aplicarla basta con decorar la propiedad del viewmodel con [NumeroPar] (sin el sufijo Attribute).

Ahora podríamos crear una vista para crear objetos DemoModel:

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

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

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

Y podríamos comprobar como en efecto tan solo podemos entrar números pares, comprendidos entre 1 y 100:


Figura 1: Validación de número par fallida

Validación propia en cliente

La diferencia del atributo NumeroPar con los que vienen de serie, como p.ej. Range es que los segundos validan en cliente antes de que se envíen los datos al servidor, mientras que los atributos propios tan solo se validan en servidor (los atributos que vienen de serie también se validan en servidor, recordad que los datos deben validarse siempre en servidor y que la validación en cliente es solo por usabilidad y no por seguridad).

Por supuesto podemos añadir validación en cliente para nuestros atributos. Llegados a este punto debo decir que el mecanismo exacto depende de la librería de validación que se use en cliente. Aquí vamos a ver cómo hacerlo usando jQuery Validate que es la librería que se incluye por defecto en ASP.NET MVC. Para usar otras librerías de validación en cliente sería necesario realizar otros pasos.

Añadiendo el código javascript

Lo primero que debemos hacer es crear el código javascript que va a validar los datos. Si usamos jQuery Validate esto equivale a añadir un validador nuevo. Para añadir un validador nuevo basta con darle un nombre y el método javascript a ejecutar:

$.validator.addMethod("numeropar", function (value, element, param) {
    return value % 2 == 0;
});

Hemos añadido un validador llamado "numeropar" con la función de validación asociada. Con esto ahora jQuery Validate sabe que debe llamar a este método javascript cuando se requiera usar el validador "numeropar".

El siguiente paso es informar a jQuery Validate cuando debe llamar a este validador "numeropar" que hemos añadido. Para ello necesitaremos código en cliente y en servidor.

La interfaz IClientValidatable

Empecemos por el código de servidor: nuestro atributo de validación debe implementar una interfaz llamada IClientValidatable. Esta interfaz requiere que implementemos un solo método llamado GetClientValidationRules, que debe devolver una colección de objetos de la clase ModelClientValidationRule.

La clase ModelClientValidationRule contiene el nombre de la regla de validación en cliente a aplicar y el mensaje de error en caso de que dicha validación falle. En nuestro caso una posible implementación es:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
      yield return new ModelClientValidationRule
       {
         ErrorMessage = this.ErrorMessage,
         ValidationType = "numeropar"
      };
   }

Estamos devolviendo una colección con un solo elemento ModelClientValidationRule. El nombre de la regla de validación en cliente es numeropar y el mensaje de error es el mismo mensaje que se usa para la validación en servidor.

Nota: ¿Conoces la palabra clave yield? Esa palabra clave de C# permite devolver colecciones sin necesidad de crear una clase que implemente la colección (usualmente una List<T>). Para más información te remito al blog de José Manuel Alarcón donde lo cuenta de forma fenomenal: http://www.jasoft.org/Blog/post/PermaLinkaspxguid=8dfbbe0c-7851-4cb8-8a49-150be21.aspx

Vale, hemos creado en cliente un validador llamado "numeropar" y hemos modificado nuestro atributo para indicar que debe usarse la regla de validación en cliente llamada "numeropar". Parece que todo debería funcionar… pero todavía nos queda un último detalle.

Código javascript no obtrusivo

Por defecto ASP.NET MVC3 no solo usa jQuery Validate para las validaciones sino que además usa la versión unobtrusive. No sé si conoces el significado de código javascript no obtrusivo (unobtrusive javascript) pero resumiéndolo te puedo decir que se trata de que el código javascript esté totalmente separado de las etiquetas HTML. Eso significa no ver más los famosos onclick="…" y similares. Las etiquetas HTML se mantienen totalmente limpias de código javascript y contienen tan solo atributos HTML estándar. El uso de javasctipt no obtrusivo se basa en una característica nueva de HTML5 (aunque es posible usar javascript no obtrusivo en versiones anteriores, aunque no de forma tan elegante). Para más información os remito a un post en mi blog donde comento más en detalle que es y cómo funciona el javascript no obtrusivo: http://geeks.ms/blogs/etomas/archive/2010/11/12/saca-tus-scripts-de-tu-c-243-digo-html.aspx

Si miras el código fuente HTML que genera la llamada a Html.EditorFor(x=>ValorPar) verás lo siguiente:

<div class="editor-field">
    <input class="text-box single-line" data-val="true" data-val-number="The field ValorPar must be a number." data-val-numeropar="El número debe ser par." data-val-range="Positivo menor que 100" data-val-range-max="100" data-val-range-min="1" data-val-required="The ValorPar field is required." id="ValorPar" name="ValorPar" type="text" value="" />
    <span class="field-validation-valid" data-valmsg-for="ValorPar" data-valmsg-replace="true"></span>
</div>

Todos los atributos data-val que contiene el <input> son para las validaciones usando javascript no obtrusivo. De hecho puedes observar que existe un atributo llamado data-val-numeropar. Ese atributo se ha generado porque precisamente hemos implementado IClientValidatable en nuestro atributo de servidor. Bien, por un lado tenemos un validador llamado "numeropar" que hemos dado de alta en jQuery Validate. Por otro tenemos el atributo data-val-numeropar que se ha generado al implementar IClientValidatable en nuestro atributo. Tan solo nos falta indicar a jQuery Validate que debe usar el validador llamado "numeropar" en todos aquellos campos que tengan el atributo "data-val-numeropar". Para ello debemos usar el siguiente código javascript:

$.validator.unobtrusive.adapters.addBool("numeropar");

Con este código se enlaza el validador numeropar de jQuery Validate, con el atributo data-val-numeropar.

De hecho en el ejemplo el mismo nombre (“numeropar”) para todo, cosa que yo os recomiendo, pero realmente tenemos dos conceptos:

  1. El nombre del validador que damos de alta en jQuery Validate
  2. El nombre de la regla de validación, es decir el nombre del atributo data-val-xx (donde xx se sustituye por el nombre de la regla de validación).
No es obligatorio usar el mismo nombre. Con el siguiente código, dais de alta un validador llamado "numeropar" y lo vinculáis a la regla de validación "np":

$.validator.addMethod("numeropar", function (value, element, param) {
    return value % 2 == 0;
    });
    $.validator.unobtrusive.adapters.addBool("np", "numeropar");

Fijaos en el segundo parámetro del método addBool: indica que la regla "np" debe validarse usando el validador "numeropar" (si no se pone el parámetro se asume que el nombre es el mismo).

Por supuesto ahora la regla se llama "np", y no "numeropar", por lo que cuando se implemente IClientValidatable debe usarse "np":

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
      yield return new ModelClientValidationRule
      {
         ErrorMessage = this.ErrorMessage,
         ValidationType = "np"
      };

Bueno, el código completo de la vista quedaría:

@model MvcApplication2.Models.DemoModel
<h2>
   ViewPage1</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
    $.validator.addMethod("numeropar", function (value, element, param) {
      return value % 2 == 0;
   });
   $.validator.unobtrusive.adapters.addBool("np", "numeropar");
</script>
@using (Html.BeginForm())
{
   @Html.ValidationSummary(true)
   <fieldset>
      <legend>DemoModel</legend>
      <div class="editor-label">
         @Html.LabelFor(model => model.ValorPar)
      </div>
      <div class="editor-field">
         @Html.EditorFor(model => model.ValorPar)
         @Html.ValidationMessageFor(model => model.ValorPar)
      </div>
      <p>
         <input type="submit" value="Create" />
      </p>
   </fieldset>
}

¡Ahora sí! Hemos creado una validación propia y que se valida no solo en servidor sino también en cliente!

En resumen…

Hemos visto cómo funciona el sistema de validaciones basadas en atributos en ASP.NET MVC3. También hemos visto lo sencillo que es crear nuestras propias validaciones usando atributos propios y como añadir validación en cliente en javascript. Por supuesto os animo a que entendáis como funciona jQuery Validate (hemos visto el tipo de validadores más sencillos que existen, los que sólo validan un valor, pero los hay que pueden recibir parámetros para validar rangos, o hacer comparaciones). No entraremos más en detalle en jQuery Validate porque cae fuera del ámbito de este manual.

¿Y sobre las validaciones? Pues no hemos terminado todavía… ¡hay un par de cosillas más que creo interesantes y que veremos en el siguiente artículo!

Eduard Tomàs

Apasionado de la informática, los videojuegos, rol y... la cerveza. Key Consulta...

Manual