1

I have 3 input boxes for someone to enter a phone number. One for the area code (3 digits), one for the prefix (3 digits), and one for the suffix(4 digits). I want to validate that the sum of the 3 fields equals 10 before saving. How can that be done using data annotations?

model:

 public string PhoneNumber
    {
        get
        {
            return _phoneNumber;
        }
        set
        {
            _phoneNumber = value;
        }
    }      
    private string _phoneNumber;
    public string Area
    {
        get
        {
            try
            {
                return _phoneNumber.Split(new char[] { '(', ')', '-' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
            }
            catch
            {
                return "";
            }
        }

    }

    public string Prefix
    {
        get
        {
            try
            {
                return _phoneNumber.Split(new char[] { '(', ')', '-' }, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
            }
            catch
            {
                return "";
            }
        }

    }

    public string Suffix
    {
        get
        {
            try
            {
                return _phoneNumber.Split(new char[] { '(', ')', '-' }, StringSplitOptions.RemoveEmptyEntries)[2].Trim();
            }
            catch
            {
                return "";
            }
        }

    }
thatdude
  • 77
  • 1
  • 2
  • 10
  • 1
    One way would be to implement [`IValidatableObject`](https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.ivalidatableobject(v=vs.110).aspx) as in [this question](http://stackoverflow.com/questions/3400542/how-do-i-use-ivalidatableobject) – stuartd Jul 18 '16 at 23:41

2 Answers2

3

You have two possible approaches here.

IValidatableObject

As mentioned by user stuartd in the comments, you could implement the IValidatableObject in your model to make your validation. In your case, your code would look something like this:

public class MyModel : IValidatableObject
{
    // Your properties go here

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // You may want to check your properties for null before doing this
        var sumOfFields = PhoneNumber.Length + Area.Length + Prefix.Length;

        if(sumOfFields != 10)
            return new ValidationResult("Incorrect phone number!");
    }
}

Custom ValidationAttribute

Since you stated that you want to use data annotations, you could implement a custom ValidationAttribute. It would go something along these lines.

public class TotalAttributesLengthEqualToAttribute : ValidationAttribute
{
    private string[] _properties;
    private int _expectedLength;
    public TotalAttributesLengthEqualToAttribute(int expectedLength, params string[] properties)
    {
        ErrorMessage = "Wrong total length";
        _expectedLength = expectedLength;
        _properties = properties;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (_properties == null || _properties.Length < 1)
        {
            return new ValidationResult("Wrong properties");
        }

        int totalLength = 0;

        foreach (var property in _properties)
        {
            var propInfo = validationContext.ObjectType.GetProperty(property);

            if (propInfo == null)
                return new ValidationResult($"Could not find {property}");

            var propValue = propInfo.GetValue(validationContext.ObjectInstance, null) as string;

            if (propValue == null)
                return new ValidationResult($"Wrong property type for {property}");

            totalLength += propValue.Length;
        }

        if (totalLength != _expectedLength)
            return new ValidationResult(ErrorMessage);

        return ValidationResult.Success;
    }
}

Than you would choose one of your properties and implement it like:

[TotalAttributesLengthEqualTo(10, nameof(PhoneNumber), nameof(Area), nameof(Prefix), ErrorMessage = "The phone number should contain 10 digits")]
public string PhoneNumber
{
   get...

Note that if your compiler does not support C# 6.0, you will have to change the strings starting with $ to string.Format, and you will have to substitute the attribute names inside nameof() for ther hardcoded names.

Community
  • 1
  • 1
André Sampaio
  • 309
  • 1
  • 5
  • you sir....you're the true mvp. One quick question, what do you mean by substituting "the attribute names inside nameof() for their hardcoded names"? – thatdude Jul 19 '16 at 22:18
  • The nameof expression gets evaluated at compile time to the string representation of the member passed to it. For instance, nameof(PhoneNumber) will be evaluated to the string "PhoneNumber". You could simply use "PhoneNumber" instead of nameof(PhoneNumber). The main advantage of using nameof is that it will be easier to maintain in case you need to change that property name in the future. – André Sampaio Jul 19 '16 at 22:36
  • I see...thanks for the explanation. However, I am getting an error telling me "An object reference is required for non-static field,method,or property 'RxCard.DataObjects.Pharmacy.Area' " when declaring the names. Sorry, I'm a novice in .net. – thatdude Jul 19 '16 at 22:56
  • I can't answer you for sure wihout looking at your code. But it looks like you are trying to call .Area inside a static method. If this is happening in the nameof(Area) and the other nameof's, try changing them to nameof(Pharmacy.Area). Otherwise, I would look for static methods trying to use Area as an instance property. – André Sampaio Jul 19 '16 at 23:08
1

You could do something like this :

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string area = (string)validationContext.ObjectType.GetProperty("Area").GetValue(validationContext.ObjectInstance, null);
        string prefix = (string)validationContext.ObjectType.GetProperty("Prefix").GetValue(validationContext.ObjectInstance, null);
        string suffix = (string)validationContext.ObjectType.GetProperty("Suffix").GetValue(validationContext.ObjectInstance, null);

        if ((area.Length + prefix.Length + suffix.Length) == 10)
        {
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult("I will not use DA for this, but there we go...");
        }
    }

Or concatenate the value first and just use the property value

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string number = (string)value;
        if (number.Length == 10)
        {
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult("I will not use DA for this, but there we go...");
        }
    }
MrVoid
  • 709
  • 5
  • 19