ASP.NET MVC: Validación personalizada por DataAnnotation

110

Tengo un modelo con 4 propiedades que son de tipo cadena. Sé que puede validar la longitud de una sola propiedad utilizando la anotación StringLength. Sin embargo, quiero validar la longitud de las 4 propiedades combinadas.

¿Cuál es la forma MVC de hacer esto con la anotación de datos?

Pregunto esto porque soy nuevo en MVC y quiero hacerlo de la manera correcta antes de crear mi propia solución.

Danny van der Kraan
fuente
2
¿Ha mirado Fluent Validation? Maneja escenarios complejos mucho mejor que las anotaciones de datos
levelnis
Eche un vistazo a las soluciones altamente recomendadas .... dotnetcurry.com/ShowArticle.aspx?ID=776
Niks
Gracias por responder. Revisaré Fluent Validation, nunca he oído hablar de él. Y Niks, Darin básicamente escribió lo que explicaba el artículo en el enlace que publicaste. Entonces, gracias ... ¡Cosas increíbles!
Danny van der Kraan

Respuestas:

177

Podría escribir un atributo de validación personalizado:

public class CombinedMinLengthAttribute: ValidationAttribute
{
    public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
        this.MinLength = minLength;
    }

    public string[] PropertyNames { get; private set; }
    public int MinLength { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
        var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
        var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
        if (totalLength < this.MinLength)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }
}

y luego puede tener un modelo de vista y decorar una de sus propiedades con él:

public class MyViewModel
{
    [CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
    public string Foo { get; set; }
    public string Bar { get; set; }
    public string Baz { get; set; }
}
Darin Dimitrov
fuente
4
Gracias por responder, acepté tu respuesta. De hecho, me siento un poco avergonzado. ¡Escribiste la solución completa! Jeje. Solo tuve que cambiar la función IsValid para verificar la longitud máxima. Entonces, ¿es esta la solución MVC aceptada para este tipo de problemas?
Danny van der Kraan
7
@DannyvanderKraan, sí, esta es la forma aceptada. Por supuesto, esto apesta tanto que nunca lo uso y uso FluentValidation.NET en su lugar para realizar la validación.
Darin Dimitrov
11
Aquí: fluentvalidation.codeplex.com . Se podría acaba de escribir un validador simple para el modelo de vista que podría haber tenido este aspecto (una sola línea de código): this.RuleFor(x => x.Foo).Must((x, foo) => x.Foo.Length + x.Bar.Length + x.Baz.Length < 20).WithMessage("The combined minimum length of the Foo, Bar and Baz properties should be longer than 20");. Ahora mire el código en mi respuesta que necesita escribir con las anotaciones de datos y dígame cuál prefiere. El modelo de validación declarativa es muy deficiente en comparación con un modelo imperativo.
Darin Dimitrov
1
Esto es un poco tarde, pero ¿alguien sabe si hay una configuración diferente que debe "activar" para permitir anotaciones de datos personalizadas? Sé cómo agregar un espacio de nombres para js discretos en el archivo web.config, pero ¿en algún otro lugar?
José
1
¡He estado buscando esto toda la mañana! Lo he implementado, y desafortunadamente cuando IsValidse llama validationContextes nulo. ¿Alguna idea de lo que hice mal? :-(
Grimm The Opiner
95

Modelo auto validado

Su modelo debe implementar una interfaz IValidatableObject. Pon tu código de validación en el Validatemétodo:

public class MyModel : IValidatableObject
{
    public string Title { get; set; }
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Title == null)
            yield return new ValidationResult("*", new [] { nameof(Title) });

        if (Description == null)
            yield return new ValidationResult("*", new [] { nameof(Description) });
    }
}

Tenga en cuenta: esta es una validación del lado del servidor . No funciona del lado del cliente. Su validación se realizará solo después del envío del formulario.

Andrei
fuente
Gracias por responder a Andrei. Si bien su solución también funcionaría, elijo Darin porque es más reutilizable.
Danny van der Kraan
6
yield return new ValidationResult ("El título es obligatorio", "Título"); agregaría el nombre de la propiedad, útil para agrupar errores de validación para mostrarlos si es necesario.
Mike Kingscott
5
Tenga en cuenta que este método de validación solo se llama después de que todos los atributos de validación hayan pasado la validación.
Pedro
3
Esto funcionó bien para mí ya que mi validación fue muy específica. Agregar un atributo personalizado habría sido una exageración para mí ya que la validación no se iba a reutilizar.
Steve S
Esto es lo que busco. ¡Gracias!
Amol Jadhav
27

ExpressiveAnnotations le brinda esa posibilidad:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
jwaliszko
fuente
¡Esto es brillante! Mis oraciones fueron respondidas :)
Korayem
Acabo de encontrar esta respuesta y me ha ahorrado mucho tiempo. ¡Las anotaciones expresivas son geniales!
Brad
10

Para mejorar la respuesta de Darin, puede ser un poco más corto:

public class UniqueFileName : ValidationAttribute
{
    private readonly NewsService _newsService = new NewsService();

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        var file = (HttpPostedFile) value;

        return _newsService.IsFileNameUnique(file.FileName);
    }
}

Modelo:

[UniqueFileName(ErrorMessage = "This file name is not unique.")]

Tenga en cuenta que se requiere un mensaje de error; de lo contrario, el error estará vacío.

Jamie
fuente
8

Antecedentes:

Las validaciones de modelos son necesarias para garantizar que los datos recibidos que recibimos sean válidos y correctos para que podamos realizar el procesamiento posterior con estos datos. Podemos validar un modelo en un método de acción. Los atributos de validación integrados son Compare, Range, RegularExpression, Required, StringLength. Sin embargo, es posible que tengamos escenarios en los que requiramos atributos de validación distintos de los integrados.

Atributos de validación personalizados

public class EmployeeModel 
{
    [Required]
    [UniqueEmailAddress]
    public string EmailAddress {get;set;}
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public int OrganizationId {get;set;}
}

Para crear un atributo de validación personalizado, tendrá que derivar esta clase de ValidationAttribute.

public class UniqueEmailAddress : ValidationAttribute
{
    private IEmployeeRepository _employeeRepository;
    [Inject]
    public IEmployeeRepository EmployeeRepository
    {
        get { return _employeeRepository; }
        set
        {
            _employeeRepository = value;
        }
    }
    protected override ValidationResult IsValid(object value,
                        ValidationContext validationContext)
    {
        var model = (EmployeeModel)validationContext.ObjectInstance;
        if(model.Field1 == null){
            return new ValidationResult("Field1 is null");
        }
        if(model.Field2 == null){
            return new ValidationResult("Field2 is null");
        }
        if(model.Field3 == null){
            return new ValidationResult("Field3 is null");
        }
        return ValidationResult.Success;
    }
}

Espero que esto ayude. Salud !

Referencias

Yasser Shaikh
fuente
1

Un poco tarde para responder, pero para quién está buscando. Puede hacer esto fácilmente usando una propiedad adicional con la anotación de datos:

public string foo { get; set; }
public string bar { get; set; }

[MinLength(20, ErrorMessage = "too short")]
public string foobar 
{ 
    get
    {
        return foo + bar;
    }
}

Eso es todo lo que es demasiado. Si realmente desea mostrar en un lugar específico el error de validación también, puede agregar esto en su vista:

@Html.ValidationMessage("foobar", "your combined text is too short")

hacer esto en la vista puede resultar útil si desea realizar la localización.

¡Espero que esto ayude!

Leo Muller
fuente