This article has been localized into Spanish by the community.
Validación de modelo personalizada
Si tú has sentido que los métodos de validación integrados, cómo lo discutimos en los artículos previos, no son suficientes, ASP.NET MVC te ofrece la posibilidad de implementar su propia lógica de validación. Puede haber muchas ocasiones dónde es una buena idea, pero tú debes desde luego primero asegurarte de que no hay una más alternativa integrada más fácil, como la opción de [RegularExpression], la cual ofrece una gran flexibilidad.
Con eso dicho, realmente no es difícil implementar tu propia lógica de validación. Hay algunas formas de hacerlo, así que examinemoslas. Expande haremos nuestros previos ejemplos en el par de artículos pasados, donde introducimos la clase Webuser. Agregaremos una propiedad Birthday, y luego le asignaremos alguna validación personalizada.
Validación personalizada con ValidationAttribute
Si tú quieres una validación similar al tipo qué está integrado o más dónde puedes agregar DataAnnotations a tus propiedades(por ejemplo [Required] o [EmailAddress]), puede simplemente crear una clase y dejarla heredar de ValidationAttribute. Después de eso, simplemente sobreescribe el método IsValid() y escribe tu propia lógica.
Para nuestro ejemplo, agregue alguna validación muy básica (y quizás ligeramente tonta) al método IsValid(), sólo para mostrarte posibilidades. Alguno de estos pudieron haber sido manejadas con la validación [Range], pero no todas de ellas, y eso es la belleza de la validación personalizada - puedes agregar tanta lógica como la necesites. También, puedes retornar mensajes de errores individuales para cada una de sus revisiones, cómo lo hago en mi ejemplo:
public class WebUserBirthdayValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
DateTime birthday = (DateTime)value;
if(birthday.Year < 1900)
return new ValidationResult("Surely you are not THAT old?");
if(birthday.Year > 2000)
return new ValidationResult("Sorry, you're too young for this website!");
if(birthday.Month == 12)
return new ValidationResult("Sorry, we don't accept anyone born in December!");
return ValidationResult.Success;
}
}
Cómo puedes ver, hacemos múltiples revisiones Y regresamos una nueva instancia de la clase ValidationResult si ella se relacionan. Sí ninguna de ellas es verdadera, podemos asumir que tenemos un valor válido, entonces regresamos un resultado ValidationResult.Success. Aplicando esta lógica de validación a nuestra propiedad Birthday de nuestra clase WebUser es tan simple como usar una de nuestros mecanismos de validación integrados:
public class WebUser
{
// Other properties here...
[WebUserBirthdayValidationAttribute]
public DateTime Birthday { get; set; }
}
Si tú quieres probarlo, sólo necesitas un controlador para manejar las solicitudes GET y POST, también una vista con un formulario para editar la propiedad, cómo esto:
public class ValidationController : Controller
{
[HttpGet]
public IActionResult CustomValidation()
{
return View();
}
[HttpPost]
public IActionResult CustomValidation(WebUser webUser)
{
if(ModelState.IsValid)
return Content("Thank you!");
else
return View(webUser);
}
}
@model HelloMVCWorld.Models.WebUser
@using(var form = Html.BeginForm())
{
<div>
@Html.LabelFor(m => m.Birthday)
@Html.TextBoxFor(m => m.Birthday)
@Html.ValidationMessageFor(m => m.Birthday)
</div>
<input type="submit" value="Submit" />
}
Felicidades, ahora han implementado lógica de validación personalizada para una de sus propiedades.
Uno de los beneficios realmente geniales de esta forma es que tu lógica de validación no está necesariamente atada a un modelo en especifico - en vez de llamar a tu clase WebUserBirthdayValidationAttribute, tú podrías haberlo llamado BirthdayValidationAttribute y reusarlo en todos los tipos de propiedades relacionadas con cumpleaños en tu proyecto. Por otra parte caerás en situaciones donde tiene mucho sentido atar tu lógica de validación a tu modelo - veremos Cómo funciona esto En las siguientes secciones de este artículo.
Validación personalizada con IValidatableObject
Como una alternativa al método que acabamos de ver, donde implementábamos una versión personalizada de ValidationAttribute, podemos hacer que nuestro modelo implemente la interface IValidatableObject . Haciendo esto, podemos contener la lógica necesaria para la validación personalizada directamente en el modelo, en vez de implementar una nueva clase. Esto tiene incluso más sentido si la lógica de validación que estás buscando agregar afecta a más de una propiedad, ya que la validación ahora no está atada a una singular, propiedad específica.
La interfaz IValidatableObject solamente tiene un método: Validate(). Éste debería contener toda la lógica que necesitas que no pueda ser aplicada a tus propiedades a través de mecanismos integrados, en otras palabras, aún eres libre de combinar validaciones personalizadas irregulares. Hemos hecho eso en esta nueva clase llamada WebUserValidatable (pero por favor nota que el nombre de la clase no tiene que incluir la palabra "Validatable" o algo como eso - aún así eres libre de llamarlo de la forma que quieras) :
public class WebUserValidatable : IValidatableObject
{
[Required(AllowEmptyStrings = true, ErrorMessage = "You must enter a value for the First Name field!")]
[StringLength(25, ErrorMessage = "The First Name must be no longer than 25 characters!")]
public string FirstName { get; set; }
[Required(ErrorMessage = "You must enter a value for the Last Name field!")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "The Last Name must be between 3 and 50 characters long!")]
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Birthday.Year < 1900)
yield return new ValidationResult("Surely you are not THAT old?", new[] { "Birthday" });
if(this.Birthday.Year > 2000)
yield return new ValidationResult("Sorry, you're too young for this website!", new[] { "Birthday" });
if(this.Birthday.Month == 12)
yield return new ValidationResult("Sorry, we don't accept anyone born in December!", new[] { "Birthday" });
}
}
Notarás dos cosas: La lógica que estoy usando básicamente es la misma que nuestro ejemplo con el ValidationAttribute, pero también hay un par de diferencias. La primera de todas, no tenemos que regresar algo si la validación es exitosa. Segundo, ahorita vimos el nombre de la propiedad (o propiedades, si hay múltiples) relevante al mensaje de error, en este caso Birthday. La razón es muy simple: Cuando la validación no está ya atada a una propiedad en específico, a través de DataAnnotations, el framework no puede saber a cuál propiedad relacionar con el error, así que proveemos esa información. Esto nos permite hacer el tipo de validación que involucra múltiples propiedades como esto:
if((this.Birthday.Month == 12) && (this.FirstName != "Santa"))
yield return new ValidationResult("Sorry, to be born in December, we require that your first name is Santa!", new[] { "Birthday", "FirstName" });
Este ejemplo es completamente., pero es una buena demostración de cuán fácilmente puedes hacer una o varias de tus revisiones de validación relacionadas a múltiples propiedades. Si tienes un mensaje de error individual en tu mercado para cada uno de los campos, los mensajes de error generados ahora serán fijados a ambos Campos.
Si tú quieres ver eso en acción, simplemente agrega la lógica al controlador y una vista con un formulario a tu proyecto:
public class ValidationController : Controller
{
[HttpGet]
public IActionResult CustomValidation()
{
return View();
}
[HttpPost]
public IActionResult CustomValidation(WebUserValidatable webUser)
{
if(ModelState.IsValid)
return Content("Thank you!");
else
return View(webUser);
}
}
@model HelloMVCWorld.Models.WebUserValidatable
@using(var form = Html.BeginForm())
{
<div>
@Html.LabelFor(m => m.FirstName)
@Html.TextBoxFor(m => m.FirstName)
@Html.ValidationMessageFor(m => m.FirstName)
</div>
<div>
@Html.LabelFor(m => m.LastName)
@Html.TextBoxFor(m => m.LastName)
@Html.ValidationMessageFor(m => m.LastName)
</div>
<div>
@Html.LabelFor(m => m.Birthday)
@Html.TextBoxFor(m => m.Birthday)
@Html.ValidationMessageFor(m => m.Birthday)
</div>
<input type="submit" value="Submit" />
}
Resumen
Implementando tu propia lógica de validación personalizada en tu proyecto ASP.NET MVC es muy fácil. Hay dos métodos, uno que se ata directamente a una propiedad en específico y otro que se ata directamente al modelo, permitiéndote validar múltiples propiedades al mismo tiempo.