Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Required Attribute on Generic List Property

Is it possible to put a [Required] attribute onto a List<> property?

I bind to a generic list on POST and was wondering if I could make ModelState.IsValid() fail if the property has 0 items in it?

like image 683
Nick Reeve Avatar asked Jun 21 '11 16:06

Nick Reeve


3 Answers

Adding the Required attribute to a list-style property doesn't really do what you want. The will complain if the list isn't created, but won't complain if the list exists with 0 item in it.

However, it should be easy enough to derive your own data annotations attribute and make it check the list for Count > 0. Something like this (not tested yet):

[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : ValidationAttribute
{
    private const string defaultError = "'{0}' must have at least one element.";
    public CannotBeEmptyAttribute ( ) : base(defaultError) //
    { 
    }

    public override bool IsValid ( object value )
    {
      IList list = value as IList;
      return ( list != null && list.Count > 0 );
    }

    public override string FormatErrorMessage ( string name )
    {
        return String.Format(this.ErrorMessageString, name);
    }
}

EDIT:

You'll also have to be careful how you bind your list in your view. For example, if you bind a List<String> to a view like this:

<input name="ListName[0]" type="text" />
<input name="ListName[1]" type="text" />
<input name="ListName[2]" type="text" />
<input name="ListName[3]" type="text" />
<input name="ListName[4]" type="text" />

The MVC model binder will always put 5 elements in your list, all String.Empty. If this is how your View works, your attribute would need to get a bit more complex, such as using Reflection to pull the generic type parameter and comparing each list element with default(T) or something.

A better alternative is to use jQuery to create the input elements dynamically.

like image 169
Michael Edenfield Avatar answered Nov 08 '22 14:11

Michael Edenfield


For those who're looking for minimalist examples:

[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        var list = value as IEnumerable;
        return list != null && list.GetEnumerator().MoveNext();
    }
}

This is modified code from the accepted answer. It is suitable in the case from the question, and in even more cases, since IEnumerable is higher in System.Collections hierarchy. Additionally, it inherits behavior from RequiredAttribute, so no need in coding it explicitly.

like image 32
moudrick Avatar answered Nov 08 '22 15:11

moudrick


For those that use C# 6.0 (and above) and who are looking for one-liners:

[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : RequiredAttribute
{
    public override bool IsValid(object value) => (value as IEnumerable)?.GetEnumerator().MoveNext() ?? false;
}
like image 5
Nick N. Avatar answered Nov 08 '22 13:11

Nick N.