Search code examples
c#asp.netasp.net-mvcvalidationvalidationattribute

How to force ValidationAttribute to mark specified object members as invalid?


I've got my model which contains some members:

public class Address
{
   public Street { get; set;}
   public City { get; set; }
   public PostalCode { get; set; }
}

Now I've got my ValidationAttribute with IsValid method overrided like this:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var input = value as Address;

    if (string.IsNullOrEmpty(input.City))
        return new ValidationResult("City is required);

    if (!string.IsNullOrEmpty(input.PostalCode))
        if (string.IsNullOrEmpty(input.Street))
            return new ValidationResult("Stret is required");

    return ValidationResult.Success;
}

The problem is:

After validation my model state adds model error only to whole Adress member, but I need it to be added to specified members like city or street.

Any help with this will be appreciated... thanks!


Solution

  • you can add memberNames !

    return new ValidationResult("City is required", new string[] { "City", "Street" });
    

    EDIT : i've tested and found a solution : System.ComponentModel.IDataErrorInfo !! this doesn't work on the javascript client, but when submitting the result is what we attempted :) so keep using the ValidationResult with memberNames and :

    public class myModel : System.ComponentModel.IDataErrorInfo
        {
    
            private Dictionary<string, List<ValidationResult>> errors;
            private bool IsValidated = false;
    
            private void Validate()
            {
                errors = new Dictionary<string, List<ValidationResult>>();
                List<ValidationResult> lst = new List<ValidationResult>();
                Validator.TryValidateObject(this, new ValidationContext(this, null, null), lst, true);
                lst.ForEach(vr =>
                    {
                        foreach (var memberName in vr.MemberNames)
                            AddError(memberName, vr);
                    });
                IsValidated = true;
            }
            private void AddError(string memberName, ValidationResult error)
            {
                if (!errors.ContainsKey(memberName))
                    errors[memberName] = new List<ValidationResult>();
                errors[memberName].Add(error);
            }
    
            public string Error
            {
                get
                {
                    if (!IsValidated)
                        Validate();
    
                    return string.Join("\n", errors
                        .Where(kvp => kvp.Value.Any())
                        .Select(kvp => kvp.Key + " : " + string.Join(", ", kvp.Value.Select(err => err.ErrorMessage)))
                        );
                }
            }
    
            public string this[string columnName]
            {
                get
                {
                    if (!IsValidated)
                        Validate();
    
                    if (errors.ContainsKey(columnName))
                    {
                        var value = errors[columnName];
                        return string.Join(", ", value.Select(err => err.ErrorMessage));
                    }
                    else
                        return null;
                }
            }
    

    and now the current property is marked in error, and the memberNames too !

    :D

    Edit 2

    In fact, you can keep simple DataAnnotations to inform client, and server side, you can add more complex business validation :

    public class myModel : IValidatableObject
    {
          public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
          {
    
                if (string.IsNullOrEmpty(input.City))
                      yield return new ValidationResult("City is required",new string[]{"prop1","prop2"});
                if (!string.IsNullOrEmpty(input.PostalCode))
                      if (string.IsNullOrEmpty(input.Street))
                            yield return new ValidationResult("Stret is required");
          }
    

    note that the Validate method is called only when DataAnnotations are all valid !