3

I have a property in a Model class something like:

    /// <summary>
    /// A list of line items in the receipt
    /// </summary>      
    public ICollection<ReceiptItem> Items { get; set; }

Is there any way I can mark up this property to validate that the collection must have 1 or more members? I am trying to avoid a manual validation function call outside of ModelState.IsValid

Yablargo
  • 3,520
  • 7
  • 37
  • 58

4 Answers4

9

I ended up solving the problem by using a custom DataAnnotation -- did not think to see if this could be done first!

Here is my code if it helps anyone else!

/// <summary>
/// Require a minimum length, and optionally a maximum length, for any IEnumerable
/// </summary>
sealed public class CollectionMinimumLengthValidationAttribute : ValidationAttribute
{
    const string errorMessage = "{0} must contain at least {1} item(s).";
    const string errorMessageWithMax = "{0} must contain between {1} and {2} item(s).";
        int minLength;
        int? maxLength;          

        public CollectionMinimumLengthValidationAttribute(int min)
        {
            minLength = min;
            maxLength = null;
        }

        public CollectionMinimumLengthValidationAttribute(int min,int max)
        {
            minLength = min;
            maxLength = max;
        }

        //Override default FormatErrorMessage Method  
        public override string FormatErrorMessage(string name)
        {
            if(maxLength != null)
            {
                return string.Format(errorMessageWithMax,name,minLength,maxLength.Value);
            }
            else
            {
                return string.Format(errorMessage, name, minLength);
            }
        }  

    public override bool IsValid(object value)
    {
        IEnumerable<object> list = value as IEnumerable<object>;

        if (list != null && list.Count() >= minLength && (maxLength == null || list.Count() <= maxLength))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
}
Yablargo
  • 3,520
  • 7
  • 37
  • 58
  • +1 for implementing as a ValidationAttribute, I changed the validation method to account for lists of enums in my model though by using code `var list = ((System.Collections.IEnumerable)value).Cast();` - this also meant checking if value was null too – KevD May 30 '12 at 15:08
  • This assumes you can enumerate `list` multiple times. – Dave Apr 21 '16 at 16:49
4

Implement IValidatableObject interface in your model class and add the custom validation logic in Validate method.

public class MyModel : IValidatableObject
{
    public ICollection<ReceiptItem> Items { get; set; }

    public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Items == null || Items.Count() == 0)
        {
            var validationResult = 
             new ValidationResult("One or more items are required") { Members = "Items"};
            return new List<ValidationResult> { validationResult };
        }

        return new List<ValidationResult>();
    }
}
Eranga
  • 32,181
  • 5
  • 97
  • 96
  • Question - do normal annotations still validate or do you expressly have to do all validation in that Validate() function now? – Yablargo Feb 01 '12 at 14:04
  • Thanks for the tips on IValidatable, I ended up doing a DataAnnotation in the end. I really didnt even think to see if I could just create my own, doh! – Yablargo Feb 01 '12 at 14:57
2

With the EF4 CodeFirst (EntityFramework.dll), you now have MinLengthAttribute and MaxLengthAttribute that you can use on array/list/collection.

Description: Specifies the minimum length of array/string data allowed in a property.

Alexandre Jobin
  • 2,811
  • 4
  • 33
  • 43
  • 2
    As a side note, at least in dnx core the MinLengthAttribute isn't working properly on things that are not explicitly Arrays (e.g. ICollection<>) – Yablargo Apr 28 '16 at 16:34
1

The simplest way to achieve this is to use the MinLength DataAnnotation in your Data Model or DTOs

[MinLength(1, ErrorMessage = "Please provide at least an item")]
public ICollection<ReceiptItem> Items { get; set; }