2010-11-24 39 views
9

J'ai créé un attribut de validation personnalisé en sous-classant ValidationAttribute. L'attribut est appliqué à mon viewmodel au niveau de la classe car il doit valider plusieurs propriétés.Impossible de définir des noms de membre à partir de l'attribut de validation personnalisé dans MVC2

Je PRÉPONDÉRANTS

protected override ValidationResult IsValid(object value, ValidationContext validationContext) 

et retour:

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

dans tous les cas où DateOfBirth est l'une des propriétés sur mon modèle de vue. Lorsque je lance mon application, je peux voir que cela se fait toucher. ModelState.IsValid est défini sur false correctement mais lorsque j'inspecte le contenu ModelState, je vois que la propriété DateOfBirth ne contient aucune erreur. Au lieu de cela, j'ai une clé de chaîne vide avec une valeur de null et une exception contenant la chaîne que j'ai spécifiée dans mon attribut de validation.

Cela n'affiche aucun message d'erreur affiché dans mon interface utilisateur lors de l'utilisation de ValidationMessageFor. Si j'utilise ValidationSummary, je peux voir l'erreur. C'est parce qu'il n'est pas associé à une propriété.

Il semblerait que j'ignore le fait que j'ai spécifié le nom du membre dans le résultat de la validation.

Pourquoi est-ce et comment le réparer?

EXEMPLE CODE puisque:

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

Répondre

2

Je ne suis pas au courant d'un moyen facile corriger ce comportement. C'est l'une des raisons pour lesquelles je déteste les annotations de données. Faire la même chose avec FluentValidation serait une paix de gâteau:

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

FluentValidation a une grande support and integration with ASP.NET MVC.

+3

Merci. Donc l'équipe MVC réutilise la classe ValidationResult mais ignore complètement l'une des propriétés? Dans l'ensemble, je suis extrêmement impressionné par la production de l'équipe MVC, mais c'est plutôt mauvais. Je viens de le vérifier dans MVC3/.NET4 et c'est toujours la même chose. –

-1

Vous devez définir la propriété ErrorMessage, donc par exemple:

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

    public DOBValidAttribute() : base(_errorMessage) 
    { 

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

Le message d'erreur est en cours de définition et fonctionne correctement, comme expliqué ci-dessus. Le problème est avec le nom de membre qui doit correspondre à la clé dans ModelState. –

+0

Pouvez-vous poster votre code alors, il pourrait nous aider à nous aider – ozz

13

bonjour à tous.

Toujours à la recherche de solution?

J'ai résolu le même problème aujourd'hui. Vous devez créer un attribut de validation personnalisé qui validera 2 dates (exemple ci-dessous). Ensuite, vous avez besoin d'un adaptateur (validateur) qui validera le modèle avec votre attribut personnalisé. Et la dernière chose est l'adaptateur de liaison avec attribut. Peut-être un exemple expliquera mieux que moi :)

on y va:

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

Beau travail. Dans MVC3, vous pouvez accéder à d'autres propriétés à partir d'un attribut de niveau de propriété en utilisant la propriété validationContext.ObjectInstance, ce qui fonctionne bien pour moi. Vous pouvez voir un exemple ici: http://favcode.net/browse/condition_validation_with_custom_validationattribute_using_.net_4.0_data_annotations_in_mvc3 –

+2

Sérieusement, C'EST la solution. – abx78

+1

moyen de faire exécuter cette validation côté client? – Donuts

0

Lorsque vous renvoyez le résultat de la validation, utilisez le constructeur à deux paramètres. Passez-lui un tableau avec le context.MemberName comme seule valeur. Espérons que cela vous aide

<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