37 votos

Modelo personalizado de validación de propiedades dependientes mediante Anotaciones de Datos

Desde ahora he usado el excelente FluentValidation biblioteca para validar mi modelo de clases. En las aplicaciones web, yo lo uso en conjunto con el jquery.validar 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á centralizado en un solo lugar.

Por este motivo estoy buscando una alternativa. Hay muchos ejemplos hay de mostrar el uso de anotaciones de datos para realizar la validación del modelo. Se ve muy prometedor. Una cosa que no pude averiguar es cómo validar una propiedad que depende de otro valor de la 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 asegurarse de que EndDate es mayor que StartDate. Yo podría escribir un personalizados validación atributo de la ampliación de ValidationAttribute con el fin de realizar la validación personalizada lógica. Lamentablemente no pude encontrar una manera de obtener la modelo de instancia:

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;
    }
}

He encontrado que la CustomValidationAttribute parece hacer el trabajo porque tiene ValidationContext de la propiedad que contiene la instancia del objeto que se está validando. Por desgracia, este atributo ha sido añadido sólo en .NET 4.0. Así que mi pregunta es: puedo conseguir la misma funcionalidad .NET 3.5 SP1?


ACTUALIZACIÓN:

Parece que FluentValidation ya soporta clientside validación de metadatos y en ASP.NET MVC 2.

Sería bueno saber si anotaciones de datos puede ser utilizada para validar dependen de las propiedades.

28voto

rowrow Puntos 224

MVC2 viene con un ejemplo de "PropertiesMustMatchAttribute" que muestra cómo obtener DataAnnotations a trabajar para usted y se debe trabajar en ambos .NET 3.5 y .NET 4.0. Que ejemplo de código se parece a esto:

[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 se utiliza este atributo, en lugar de ponerlo en una propiedad de la clase del modelo, te lo pones en la misma clase:

[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 "IsValid" se llama atributo personalizado, todo el modelo de instancia que se pasa de modo que usted puede conseguir el dependiente de los valores de la propiedad de esa manera. Usted puede fácilmente seguir este patrón para crear una fecha de comparación de atributos, o incluso más general de la comparación de atributos.

Brad Wilson tiene un buen ejemplo en su blog que muestra cómo añadir el lado del cliente parte de la validación, así, aunque no estoy seguro de si ese ejemplo de trabajo en ambos .NET 3.5 y .NET 4.0.

14voto

Nick Riggs Puntos 1051

Tuve este mismo problema y, recientemente, de código abierto mi solución: http://foolproof.codeplex.com/

Infalible de la solución para el ejemplo anterior sería:

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

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}

7voto

orcy Puntos 678

En lugar de la PropertiesMustMatch la CompareAttribute que puede ser utilizado en MVC3. De acuerdo a 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, muy útil validador que no es realmente parte de Sistema.ComponentModel.DataAnnotations, pero ha sido añadido a la Sistema.Web.Mvc DLL por el equipo. Mientras no especialmente bien el nombre (el único la comparación se hace es comprobar que la igualdad, por lo que tal vez sería EqualTo más evidente), es fácil ver a partir de el uso que este validador comprueba que el valor de una propiedad es igual a el valor de otra propiedad. Usted puede ver en el código, que el atributo toma en una cadena de propiedad que es el nombre de la otra propiedad que usted está comparando. El clásico de uso de este tipo de validador es lo que la están utilizando para aquí: contraseña la confirmación.

3voto

Steven Puntos 56939

Debido a que los métodos de la DataAnnotations .NET 3.5 no permite el suministro del objeto real validado o una validación de contexto, usted tendrá que hacer un poco de artimañas para lograr esto. Debo admitir que no estoy familiarizado con ASP.NET MVC, así que no puedo decir cómo hacerlo exactamente en conjunción con MCV, pero usted puede intentar usar un hilo de un valor estático para pasar el argumento en sí mismo. Aquí está un ejemplo con algo que podría funcionar.

Primero crear una especie de 'ámbito del objeto' que le permite pasar objetos a su alrededor sin tener que pasar 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 el validador para utilizar 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 es pasado a través de la ContextScope:

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

Es esto útil?

1voto

Jaroslaw Waliszko Puntos 6618

Tomó un poco de tiempo ya que tu pregunta, pero si usted todavía como metadatos (al menos a veces), abajo hay otra solución alternativa, que le permite ofrecer diversas expresiones lógicas para los atributos:

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

Funciona para el servidor, así como para el lado del cliente. Más detalles se pueden encontrar aquí.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X