Required Attribute on Generic List Property
Asked Answered
H

4

36

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?

Hoot answered 21/6, 2011 at 16:41 Comment(1)
If you change your model to use an array instead of a List you can use the MinLengthAttributeArchiplasm
M
36

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.

Metamorphosis answered 21/6, 2011 at 17:1 Comment(3)
I don't suppose you could elaborate on that edit?Bak
I'm not sure what edit you're asking about... your link just goes to a question.Metamorphosis
Yeah, the question is (potentially -- that's partially what I'm asking) related.Bak
M
29

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.

Martens answered 31/1, 2014 at 14:32 Comment(4)
The most Generic solution, Thanks!Orangery
Smart answer but not friendly with front-end validationLandon
one improvement is to also call base.IsValid(value) as well so that base class behavior is maintained. For C# 8's users, this can be as simple as return base.IsValid(value) && value is IEnumerable seq && seq.GetEnumerator().MoveNext();Raveaux
Add "using System.Collections;" if compiler is complaining.Snip
B
6

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;
}
Buckingham answered 21/12, 2016 at 10:34 Comment(1)
You mean five-liner? :-)Diplococcus
W
3

Modified @moudrick implementation for my requirement

Required Validation Attribute for List and checkbox List

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

If you have checkbox list

[AttributeUsage(AttributeTargets.Property)]
public sealed class CustomCheckBoxListRequiredAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        bool result = false;

        var list = value as IEnumerable<CheckBoxViewModel>;
        if (list != null && list.GetEnumerator().MoveNext())
        {
            foreach (var item in list)
            {
                if (item.Checked)
                {
                    result = true;
                    break;
                }
            }
        }

        return result;
    }
}

Here is my View Model

public class CheckBoxViewModel
{        
    public string Name { get; set; }
    public bool Checked { get; set; }
}

Usage

[CustomListRequiredAttribute(ErrorMessage = "Required.")]
public IEnumerable<YourClass> YourClassList { get; set; }

[CustomCheckBoxListRequiredAttribute(ErrorMessage = "Required.")]
public IEnumerable<CheckBoxViewModel> CheckBoxRequiredList { get; set; }
Womanize answered 12/12, 2015 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.