2010-11-24 18 views
9

He creado un atributo de validación personalizado mediante la subclase de ValidationAttribute. El atributo se aplica a mi modelo de vista en el nivel de clase, ya que necesita validar más de una propiedad.No se pueden establecer los nombres de los miembros del atributo de validación personalizado en MVC2

estoy preponderantes con

protected override ValidationResult IsValid(object value, ValidationContext validationContext) 

y volviendo:

new ValidationResult("Always Fail", new List<string> { "DateOfBirth" }); 

en todos los casos en los que DateOfBirth es una de las propiedades de mi modelo de vista.

Cuando ejecuto mi aplicación, puedo ver que se está recibiendo un golpe. ModelState.IsValid se establece en falso correctamente, pero cuando inspecciono el contenido de ModelState, veo que Property DateOfBirth NO contiene ningún error. En cambio, tengo una clave de cadena vacía con un valor de nulo y una excepción que contiene la cadena que especifiqué en mi atributo de validación.

Esto no genera ningún mensaje de error en mi interfaz de usuario al usar ValidationMessageFor. Si uso ValidationSummary, entonces puedo ver el error. Esto es porque no está asociado con una propiedad.

Parece que está ignorando el hecho de que he especificado el nombre de miembro en el resultado de la validación.

¿Por qué es esto y cómo lo arreglo?

EJEMPLO DE CÓDIGO SOLICITADA:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 
    public class ExampleValidationAttribute : ValidationAttribute 
    { 
     protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
     { 
      // note that I will be doing complex validation of multiple properties when complete so this is why it is a class level attribute 
      return new ValidationResult("Always Fail", new List<string> { "DateOfBirth" }); 
     } 
    } 

    [ExampleValidation] 
    public class ExampleViewModel 
    { 
     public string DateOfBirth { get; set; } 
    } 

Respuesta

2

No estoy al tanto de una manera fácil de solucionar este comportamiento. Esa es una de las razones por las que odio las anotaciones de datos. Hacer lo mismo con FluentValidation sería una paz de la torta:

public class ExampleViewModelValidator: AbstractValidator<ExampleViewModel> 
{ 
    public ExampleViewModelValidator() 
    { 
     RuleFor(x => x.EndDate) 
      .GreaterThan(x => x.StartDate) 
      .WithMessage("end date must be after start date"); 
    } 
} 

FluentValidation tiene un gran support and integration with ASP.NET MVC.

+3

Gracias. Entonces, ¿el equipo de MVC está reutilizando la clase ValidationResult pero haciendo caso omiso de una de las propiedades? En general, estoy muy impresionado por la salida del equipo de MVC, pero esto es bastante malo. Acabo de comprobarlo en MVC3/.NET4 y sigue siendo el mismo. –

-1

Tiene que establecer la propiedad ErrorMessage, así por ejemplo:

public class DOBValidAttribute : ValidationAttribute 
{ 
    private static string _errorMessage = "Date of birth is a required field."; 

    public DOBValidAttribute() : base(_errorMessage) 
    { 

    } 
//etc......overriding IsValid.... 
+0

El mensaje de error se está estableciendo y viene bien como se explicó anteriormente. El problema es con el nombre de miembro que debe correlacionarse con la clave en el Estado modelo. –

+0

¿Puede publicar su código, entonces, podría hacer que sea más fácil para nosotros ayudar – ozz

13

hola a todos.

¿Sigue buscando una solución?

He resuelto el mismo problema hoy. Debe crear un atributo de validación personalizado que valide 2 fechas (ejemplo a continuación). Luego necesita el Adaptador (validador) que validará el modelo con su atributo personalizado. Y lo último es vincular el adaptador con el atributo. Tal vez algún ejemplo explicará mejor que yo :)

Aquí vamos:

DateCompareAttribute.cs:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 
public class DateCompareAttribute : ValidationAttribute 
{ 
    public enum Operations 
    { 
     Equals,    
     LesserThan, 
     GreaterThan, 
     LesserOrEquals, 
     GreaterOrEquals, 
     NotEquals 
    }; 

    private string _From; 
    private string _To; 
    private PropertyInfo _FromPropertyInfo; 
    private PropertyInfo _ToPropertyInfo; 
    private Operations _Operation; 

    public string MemberName 
    { 
     get 
     { 
      return _From; 
     } 
    } 

    public DateCompareAttribute(string from, string to, Operations operation) 
    { 
     _From = from; 
     _To = to; 
     _Operation = operation; 

     //gets the error message for the operation from resource file 
     ErrorMessageResourceName = "DateCompare" + operation.ToString(); 
     ErrorMessageResourceType = typeof(ValidationStrings); 
    } 

    public override bool IsValid(object value) 
    { 
     Type type = value.GetType(); 

     _FromPropertyInfo = type.GetProperty(_From); 
     _ToPropertyInfo = type.GetProperty(_To); 

     //gets the values of 2 dates from model (using reflection) 
     DateTime? from = (DateTime?)_FromPropertyInfo.GetValue(value, null); 
     DateTime? to = (DateTime?)_ToPropertyInfo.GetValue(value, null); 

     //compare dates 
     if ((from != null) && (to != null)) 
     { 
      int result = from.Value.CompareTo(to.Value); 

      switch (_Operation) 
      { 
       case Operations.LesserThan: 
        return result == -1; 
       case Operations.LesserOrEquals: 
        return result <= 0; 
       case Operations.Equals: 
        return result == 0; 
       case Operations.NotEquals: 
        return result != 0; 
       case Operations.GreaterOrEquals: 
        return result >= 0; 
       case Operations.GreaterThan: 
        return result == 1; 
      } 
     } 

     return true; 
    } 

    public override string FormatErrorMessage(string name) 
    { 
     DisplayNameAttribute aFrom = (DisplayNameAttribute)_FromPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault(); 
     DisplayNameAttribute aTo = (DisplayNameAttribute)_ToPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault(); 

     return string.Format(ErrorMessageString, 
      !string.IsNullOrWhiteSpace(aFrom.DisplayName) ? aFrom.DisplayName : _From, 
      !string.IsNullOrWhiteSpace(aTo.DisplayName) ? aTo.DisplayName : _To); 
    } 
} 

DateCompareAttributeAdapter.cs:

public class DateCompareAttributeAdapter : DataAnnotationsModelValidator<DateCompareAttribute> 
{ 
    public DateCompareAttributeAdapter(ModelMetadata metadata, ControllerContext context, DateCompareAttribute attribute) 
     : base(metadata, context, attribute) { 
    } 

    public override IEnumerable<ModelValidationResult> Validate(object container) 
    { 
     if (!Attribute.IsValid(Metadata.Model)) 
     { 
      yield return new ModelValidationResult 
      { 
       Message = ErrorMessage, 
       MemberName = Attribute.MemberName 
      }; 
     } 
    } 
} 

Global.asax:

protected void Application_Start() 
{ 
    // ... 
    DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(DateCompareAttribute), typeof(DateCompareAttributeAdapter)); 
} 

CustomViewModel.cs:

[DateCompare("StartDateTime", "EndDateTime", DateCompareAttribute.Operations.LesserOrEquals)] 
public class CustomViewModel 
{ 
    // Properties... 

    public DateTime? StartDateTime 
    { 
     get; 
     set; 
    } 

    public DateTime? EndDateTime 
    { 
     get; 
     set; 
    } 
} 
+0

Buen trabajo. En MVC3 puede acceder a otras propiedades desde un atributo de nivel de propiedad utilizando la propiedad validationContext.ObjectInstance, que funciona bien para mí. Puede ver un ejemplo aquí: http://favcode.net/browse/condition_validation_with_custom_validationattribute_using_.net_4.0_data_annotations_in_mvc3 –

+2

En serio, ESTA es la solución. – abx78

+1

¿hay alguna forma de que esta validación se ejecute en el lado del cliente? – Donuts

0

Al devolver el resultado de la validación, utilice el constructor de dos parámetros. Páselo una matriz con el context.MemberName como el único valor. Espero que esto ayude

<AttributeUsage(AttributeTargets.Property Or AttributeTargets.Field, AllowMultiple:=False)> 


Public Class NonNegativeAttribute 
Inherits ValidationAttribute 
Public Sub New() 


End Sub 
Protected Overrides Function IsValid(num As Object, context As ValidationContext) As ValidationResult 
    Dim t = num.GetType() 
    If (t.IsValueType AndAlso Not t.IsAssignableFrom(GetType(String))) Then 

     If ((num >= 0)) Then 
      Return ValidationResult.Success 
     End If 
     Return New ValidationResult(context.MemberName & " must be a positive number",  New String() {context.MemberName}) 

    End If 

    Throw New ValidationException(t.FullName + " is not a valid type. Must be a number") 
End Function 

End Class 
Cuestiones relacionadas