viernes, 4 de mayo de 2012

ASP.NET MVC4 Validando que un campo sea true o checked

Parece que todo lo que me propongo hacer últimamente se convierte en una odisea, por muy fácil que pueda parecer al principio. Tras mi anterior aventura para validar que dos campos sean iguales en un formulario de registro esta vez le ha tocado el turno a la validación de un valor en un checkbox. Supongamos una vez más un modelo bastante simple

Modelo
public class IndexModel
{
  public bool AceptoCondiciones { get; set; }
}
Vista
@using (Html.BeginForm())
{   
  <div>@Html.CheckBoxFor(m => m.AceptoCondiciones) Acepto las condiciones de uso</div> 
  <div>@Html.ValidationMessageFor(m => m.AceptoCondiciones)</div>
 
  <input type="submit" value="Enviar" />
}
La idea es comprobar que el usuario acepta las condiciones de uso. Algo bastante típico en formularios de registro. La primera idea fue marcar la propiedad AceptoCondiciones con el DataAnnotation [Required], pero tras escribirlo (no hizo falta ni probarlo) me di cuenta que no iba a funcionar, ya que el checkbox siempre tiene un valor, o verdadero o falso (marcado o desmarcado) por lo que la regla Required no nos valdría.

Tras mirar y mirar por google y darme cuenta que ya llevaba bastante tiempo perdido no me quedo más remedio que lanzarme a hacer mi propio validador. Para hacer una validador propio tan solo debemos heredar de la clase ValidationAttribute y sobreescribir el método IsValid. Pensando en el futuro añadí una propiedad ValidValue que nos indica cual es el valor que damos por válido, no sea que alguna vez necesite tener desmarcado un check. Con esto el validador quedaría así
public class IsTrueOrFalseAttribute : ValidationAttribute
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }
}
Y añadimos el atributo al modelo
public class IndexModel
{
  [IsTrueOrFalse(ValidValue=true)]
  public bool AceptoCondiciones { get; set; }
}
Con esto esto ya podemos probar nuestro validador y ver que funciona de la manera esperada


Pero, siempre hay un pero, este validador presenta un problema y es que siempre irá al servidor para realizar la validación. En mi caso no hay problema porque en el formulario de registro siempre voy al servidor para comprobar algunas cosas, como el nombre de usuario, pero en otros escenarios puede ser que no tengamos que ir al servidor para nada, e ir para solo comprobar si un checkbox está marcado es innecesario.


Como me gusta investigar y aprender cosas nuevas, decidí hacer que mi validador también tuviera soporte para cliente. La verdad es que esta parte está bastante mal documentada pero básicamente lo primero que tenemos que hacer es que nuestro validador implemente la interfaz IClientValidatable. En nuestro caso el validador quedaría de la siguiente manera
public class IsTrueOrFalseAttribute : ValidationAttribute, IClientValidatable
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }

  IEnumerable<modelclientvalidationrule> IClientValidatable.GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  {
    var rule = new ModelClientValidationRule
    {
        ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.PropertyName) : ErrorMessage,
        ValidationType = "istrueorfalse"
    };
    // Transformamos el valor boolean a un string en minúsculas para que no falle la transformación en cliente de string a boolean.
    rule.ValidationParameters["validvalue"] = (this.ValidValue ? "true" : "false");

    yield return rule;
  }
}
Y luego en un fichero .js debemos añadir el siguiente código
$.validator.addMethod("istrueorfalse", function (value, element, param) {
  // Comparamos el valor checked del elemento con el valor permitido pasado por parámetro
  return element.checked == Boolean(param.validvalue);
});

$.validator.unobtrusive.adapters.add('istrueorfalse', ['validvalue'], function (options) {
  options.rules['istrueorfalse'] = options.params;
  options.messages['istrueorfalse'] = options.message;
});
Si ejecutamos nuestro código veremos que realiza la validación sin la necesidad de ir a cliente.

Nota: Para activar las validaciones en cliente (pese a que en ASP.NET MVC4 ya viene por defecto) debemos asegurarnos que tenemos estas lineas en el web.config
<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
También hay que comprobar que tenemos añadidas las referencias a los scripts jquery-1.6.2.js, jquery.validate.js, jquery.validate.unobtrusive.js y al fichero que creemos para añadir las lineas donde añadimos el validador y el adaptador.

Como vemos tampoco es extraordinariamente complicado validar nuestros datos en cliente así que, lo mejor que podemos hacer es hacerlo...

Happy coding!

No hay comentarios:

Publicar un comentario