CallumVass
CallumVass

Reputation: 11448

Array must contain 1 element

I have the following class:

public class CreateJob
{
    [Required]
    public int JobTypeId { get; set; }
    public string RequestedBy { get; set; }
    public JobTask[] TaskDescriptions { get; set; }
}

I'd like to have a data annotation above TaskDescriptions so that the array must contain at least one element? Much like [Required]. Is this possible?

Upvotes: 57

Views: 36125

Answers (9)

Gabriel GM
Gabriel GM

Reputation: 6639

Starting with .net 8, you can use System.ComponentModel.DataAnnotations.LengthAttribute.

This attribute now works with String AND Collection. You then provide a minimum and a maximum length to the attribute.

Usage:

[Length(1, int.MaxValue)]
public IEnumerable<int> TaskDescriptions { get; set; }

Upvotes: 2

mynkow
mynkow

Reputation: 4548

Here is a bit improved version of @dove solution which handles different types of collections such as HashSet, List etc...

public class MustHaveOneElementAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var collection = value as System.Collections.IEnumerable;
        if (collection != null && collection.GetEnumerator().MoveNext())
        {
            return true;
        }
        return false;
    }
}

Upvotes: 7

dove
dove

Reputation: 20674

I've seen a custom validation attribute used for this before, like this:

(I've given sample with a list but could be adapted for array or you could use list)

public class MustHaveOneElementAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var list = value as IList;
        if (list != null)
        {
            return list.Count > 0;
        }
        return false;
    }
}

[MustHaveOneElementAttribute (ErrorMessage = "At least a task is required")]
public List<Person> TaskDescriptions { get; private set; }

// as of C# 8/9 this could be more elegantly done with     
public class MustHaveOneElementAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return value is IList {Count: > 0};
    }
}

Credit to Antonio Falcão Jr. for elegance

Upvotes: 40

Just updating Dove's (@dove) response to C# 9 syntax:

    public class MustHaveOneElementAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
            => value is IList {Count: > 0};
    }

Upvotes: 1

M.R.T
M.R.T

Reputation: 815

You have to use 2 standard annotation attribute

public class CreateJob
{
    [MaxLength(1), MinLength(1)]
    public JobTask[] TaskDescriptions { get; set; }
}

Upvotes: 4

Sat
Sat

Reputation: 1300

It can be done using standard Required and MinLength validation attributes, but works ONLY for arrays:

public class CreateJob
{
    [Required]
    public int JobTypeId { get; set; }
    public string RequestedBy { get; set; }
    [Required, MinLength(1)]
    public JobTask[] TaskDescriptions { get; set; }
}

Upvotes: 87

Chris Pickford
Chris Pickford

Reputation: 8991

Further to mynkow's answer, I've added the ability to pass a minimum count value to the attribute and produce meaningful failure messages:

public class MinimumElementsRequiredAttribute : ValidationAttribute
{
  private readonly int _requiredElements;

  public MinimumElementsRequiredAttribute(int requiredElements)
  {
    if (requiredElements < 1)
    {
      throw new ArgumentOutOfRangeException(nameof(requiredElements), "Minimum element count of 1 is required.");
    }

    _requiredElements = requiredElements;
  }

  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    if (!(value is IEnumerable enumerable))
    {
      return new ValidationResult($"The {validationContext.DisplayName} field is required.");
    }

    int elementCount = 0;
    IEnumerator enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext())
    {
      if (enumerator.Current != null && ++elementCount >= _requiredElements)
      {
        return ValidationResult.Success;
      }
    }

    return new ValidationResult($"At least {_requiredElements} elements are required for the {validationContext.DisplayName} field.");
  }
}

Use it like this:

public class Dto
{
  [MinimumElementsRequired(2)]
  public IEnumerable<string> Values { get; set; }
}

Upvotes: 1

Y. Cotton
Y. Cotton

Reputation: 1

MinLength attribute considers the value as valid if it's null. Therefore just initialize your property in the model as an empty array and it'll work.

MinLength(1, ErrorMessageResourceName = nameof(ValidationErrors.AtLeastOneSelected), ErrorMessageResourceType = typeof(ValidationErrors))]
int[] SelectedLanguages { get; set; } = new int[0];

Upvotes: -1

Sven
Sven

Reputation: 2515

Please allow me a side note on using MinLengthAttribute with .NET Core.

Microsoft recommends using Razor Pages starting with .NET Core 2.0.

Currently, The validation with MinLengthAttribute on a property within the PageModel does not work:

[BindProperty]
[Required]
[MinLength(1)]
public IEnumerable<int> SelectedStores { get; set; }

ModelState.IsValid returns true when SelectedStores.Count() == 0.

Tested with .NET Core 2.1 Preview 2.

Upvotes: 6

Related Questions