Validación de modelos personalizados de propiedades dependientes mediante Anotaciones de datos


Desde ahora he utilizado la excelente FluentValidación biblioteca para validar mis clases modelo. En aplicaciones web lo uso junto con el jquery .validate plugin para realizar la validación del lado del cliente también. Un inconveniente es que gran parte de la lógica de validación se repite en el lado del cliente y ya no está centralizada en un solo lugar.

Por esta razón estoy buscando una alternativa. Hay muchos ejemplos allí que muestran el uso de anotaciones de datos para realizar la validación del modelo. Parece muy prometedor. Una cosa que no pude averiguar es cómo validar una propiedad que depende de otro valor de propiedad.

Tomemos por ejemplo el siguiente modelo:

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }
    [Required]
    public DateTime? EndDate { get; set; }
}

Me gustaría asegurar que EndDate es mayor que StartDate. Podría escribir una costumbre atributo de validación extendiendo ValidationAttribute para realizar una lógica de validación personalizada. Desafortunadamente no pude encontrar una manera de obtener el instancia del modelo:

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // value represents the property value on which this attribute is applied
        // but how to obtain the object instance to which this property belongs?
        return true;
    }
}

Encontré que el CustomValidationAttribute parece hacer el trabajo porque tiene esta propiedad ValidationContext que contiene la instancia del objeto que se está validando. Desafortunadamente, este atributo solo se ha agregado en.NET 4.0. Así que mi pregunta es: ¿puedo lograr la misma funcionalidad en. NET 3.5 SP1?


ACTUALIZACIÓN:

Parece que FluentValidation ya soporta la validación del cliente y los metadatos en ASP.NET MVC 2.

Sin embargo, sería bueno saber si las anotaciones de datos podrían usarse para validar propiedades dependientes.

Author: Darin Dimitrov, 2010-02-17

5 answers

MVC2 viene con un ejemplo de "PropertiesMustMatchAttribute" que muestra cómo hacer que las anotaciones de datos funcionen para usted y debería funcionar tanto en.NET 3.5 como en. NET 4.0. Ese código de ejemplo se ve así:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";

    private readonly object _typeId = new object();

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
        : base(_defaultErrorMessage)
    {
        OriginalProperty = originalProperty;
        ConfirmProperty = confirmProperty;
    }

    public string ConfirmProperty
    {
        get;
        private set;
    }

    public string OriginalProperty
    {
        get;
        private set;
    }

    public override object TypeId
    {
        get
        {
            return _typeId;
        }
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
            OriginalProperty, ConfirmProperty);
    }

    public override bool IsValid(object value)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
        object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
        object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
        return Object.Equals(originalValue, confirmValue);
    }
}

Cuando usas ese atributo, en lugar de ponerlo en una propiedad de tu clase model, lo pones en la clase misma:

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
    public string NewPassword { get; set; }
    public string ConfirmPassword { get; set; }
}

Cuando se llama a "isValid" en su atributo personalizado, se le pasa toda la instancia del modelo para que pueda obtener los valores de propiedad dependientes que manera. Puede seguir fácilmente este patrón para crear un atributo de comparación de fechas, o incluso un atributo de comparación más general.

Brad Wilson tiene un buen ejemplo en su blog que muestra cómo agregar la parte del lado del cliente de la validación también, aunque no estoy seguro de si ese ejemplo funcionará tanto en.NET 3.5 como en. NET 4.0.

 29
Author: Travis Illig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 00:16:44

Tuve este mismo problema y recientemente abrí mi solución: http://foolproof.codeplex.com /

La solución infalible del ejemplo anterior sería:

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}
 14
Author: Nick Riggs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-06 13:19:49

En lugar de las PropertiesMustMatch el CompareAttribute que se puede utilizar en MVC3. De acuerdo con este enlace http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1 :

public class RegisterModel
{
    // skipped

    [Required]
    [ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }                       

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
    public string ConfirmPassword { get; set; }
}

CompareAttribute es un nuevo validador muy útil que en realidad no es parte de Sistema.ComponentModel.DataAnnotations, pero se ha añadido a la Sistema.Web.Mvc DLL por el equipo. Mientras no particularmente bien nombrado (el único comparación que hace es para comprobar igualdad, así que tal vez equalTo sería más obvio), es fácil de ver desde el uso que comprueba este validador que el valor de una propiedad es igual a el valor de otra propiedad. Puedes ver en el código, que el atributo toma una propiedad string que es el nombre de la otra propiedad que estás comparando. El uso clásico de este tipo de validador es lo que lo están usando para aquí: contraseña confirmación.

 7
Author: orcy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-23 04:28:55

Debido a que los métodos de las anotaciones de datos de.NET 3.5 no le permiten proporcionar el objeto real validado o un contexto de validación, tendrá que hacer un poco de truco para lograr esto. Debo admitir que no estoy familiarizado con ASP.NET MVC, por lo que no puedo decir cómo hacer esto exactamente en conjunto con MCV, pero se puede tratar de usar un valor de hilo estático para pasar el argumento en sí. Aquí hay un ejemplo con algo que podría funcionar.

Primero cree algún tipo de 'ámbito de objeto' que permita para pasar objetos sin tener que pasarlos a través de la pila de llamadas:

public sealed class ContextScope : IDisposable 
{
    [ThreadStatic]
    private static object currentContext;

    public ContextScope(object context)
    {
        currentContext = context;
    }

    public static object CurrentContext
    {
        get { return context; }
    }

    public void Dispose()
    {
        currentContext = null;
    }
}

A continuación, cree su validador para usar el ContextScope:

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
         Event e = (Event)ObjectContext.CurrentContext;

         // validate event here.
    }
}

Y por último, pero no menos importante, asegúrese de que el objeto está pasado a través del ContextScope:

Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}

Es esto útil?

 3
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 17:00:58

Tomó un poco de tiempo desde que se hizo su pregunta, pero si todavía le gustan los metadatos (al menos a veces), a continuación hay otra solución alternativa, que le permite proporcionar varias expresiones lógicas a los atributos:

[Required]
public DateTime? StartDate { get; set; }    
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }

Funciona tanto para el servidor como para el cliente. Más detalles se pueden encontrar aquí.

 3
Author: jwaliszko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-09 15:23:01