2010-02-17 16 views
41

Desde ahora he utilizado la excelente biblioteca FluentValidation para validar mis clases de modelo. En las aplicaciones web lo uso junto con el complemento jquery.validate para realizar la validación del lado del cliente también. Una desventaja 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.Validación de modelo personalizado de propiedades dependientes usando Anotaciones de datos

Por esta razón estoy buscando una alternativa. Hay many ejemplos de there que muestran 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 propiedad.

Tomemos por ejemplo el siguiente modelo:

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

quisiera asegurar que EndDate es mayor que StartDate. Podría escribir un atributo de validación personalizado que extienda ValidationAttribute para realizar una lógica de validación personalizada. Por desgracia no pude encontrar una manera de obtener la instancia 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, ya que tiene esta propiedad ValidationContext que contiene la instancia del objeto que se está validando. Lamentablemente, este atributo se ha agregado solo en .NET 4.0. Entonces mi pregunta es: ¿puedo lograr la misma funcionalidad en .NET 3.5 SP1?


ACTUALIZACIÓN:

Parece que FluentValidation already supports la validación del lado del cliente y los metadatos en ASP.NET MVC 2.

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

+0

¿usted o alguien ha descubierto una forma de obtener dataanotaciones y FluentValidation trabajando (para la validación) juntos en la misma clase/modelo? si eso fuera fantástico, tengo un hilo sobre esta discusión con el autor de FV Jeremy, puedes verlo aquí: http://fluentvalidation.codeplex.com/Thread/View.aspx?ThreadId=212371 –

Respuesta

28

MVC2 viene con un ejemplo "PropertiesMustMatchAttribute" que muestra cómo funciona DataAnnotations para usted y debería funcionar en .NET 3.5 y .NET 4.0.El código de muestra es el siguiente:

[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, que lo puso en la propia 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 "recibe un llamado en su atributo personalizado, se le pasa toda la instancia del modelo para que pueda obtener los valores de propiedad dependientes de esa manera. Podría 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 has a good example on his blog que muestra cómo agregar la parte del lado del cliente de la validación también, aunque no estoy seguro si ese ejemplo funcionará en .NET 3.5 y .NET 4.0.

+2

lo he intentado pero nunca puedo obtener el error de validación para mostrar en mis páginas/vistas aspx. He intentado llamar a validationmessage para usar una cadena vacía, también he intentado usar el resumen de validación y no se muestra allí (como lo hace en el ejemplo de propiedades de compatibilidad) –

+1

Perdí unas buenas horas tratando de hacer que esto funcionara y pensando que mi código era incorrecto, hasta que finalmente me di cuenta de que simplemente no lo estaba probando correctamente cuando vi esta publicación: http://stackoverflow.com/questions/3586324/custom-validation-attribute-is-not-called-asp-net-mvc (básicamente el la validación de nivel de campo/propiedad se activa primero, por lo que necesita que validen completamente antes de que active su atributo de nivel de clase isvalid()). – jimasp

+0

Más tarde encontré una publicación mejor en la validación de clase después del problema de validación de campo aquí: http://stackoverflow.com/questions/3099397/property-level-validation-errors-hinder-the-validation-of-class-level-validation – jimasp

3

Dado que los métodos de las Anotaciones de datos de .NET 3.5 no le permiten suministrar 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 hacerlo exactamente en conjunción con MCV, pero puede intentar usar un valor estático de subprocesos para pasar el argumento en sí. Aquí hay un ejemplo con algo que podría funcionar.

En primer lugar crear una especie de 'ámbito de los objetos' 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, crear un 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 alrededor a través de la ContextScope:

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

¿Esto es útil?

+0

Steven, esto se ve bien. Lo único que modificaría es almacenar el contexto actual en HttpContext en lugar de usar 'ThreadStatic'. Simplemente lo evitaría en una aplicación ASP.NET. –

+0

¿Puede explicar por qué cree que debemos evitar esto en una aplicación ASP.NET?Uso esta construcción en mis propias aplicaciones de producción, así que estoy muy interesado por qué esto es malo. – Steven

+4

Hay muchos artículos en internet sobre por qué esto es malo. Aquí hay una: http://www.hanselman.com/blog/ATaleOfTwoTechniquesTheThreadStaticAttributeAndSystemWebHttpContextCurrentItems.aspx El problema con ThreadStatic es que en ASP.NET no tienes control sobre la vida del hilo y, como los hilos se reutilizan, hay circunstancias en las que la variable podría ser modificado Las cosas se ponen aún más feas si usa páginas y controladores asincrónicos. Por ejemplo, una solicitud puede comenzar en un hilo y terminar en un hilo diferente. Por lo tanto, en ASP.NET, la ** única ** forma de tener un verdadero almacenamiento por solicitud es HttpContext. –

14

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

solución a prueba de tontos para el ejemplo anterior sería:

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

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

Creo que la validación de la fecha GreaterThan solo funciona con las fechas de formato de Estados Unidos – GraemeMiller

7

En lugar de la PropertiesMustMatch la 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 no es en realidad parte de System.ComponentModel.DataAnnotations, pero ha sido añadido a la System.Web .Mvc DLL por el equipo. Mientras no particularmente bien llamada (el único comparación que hace es comprobar que igualdad, por lo que quizás EqualTo sería más evidente), es fácil de ver desde el uso que este validador verifica que el valor de una propiedad es igual a el valor de otra propiedad. Puede ver en el código que el atributo tiene una propiedad de cadena que es el nombre de la otra propiedad que está comparando. El uso clásico de este tipo de validador es lo que lo estamos utilizando aquí: contraseña confirmación.

3

tomó un poco de tiempo desde que se le pidió a su pregunta, pero si aún así como metadatos (por lo menos a veces), a continuación hay otra solución alternativa, que le proporciona diversas 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 can be found here.

+0

muchas gracias esta biblioteca es muy útil para casi todo. – Enzero

Cuestiones relacionadas