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:

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.
$.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 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.
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.
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:
$.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!
¿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!