Reputation: 11448
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
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
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
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
Reputation: 338
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
Reputation: 815
You have to use 2 standard annotation attribute
public class CreateJob
{
[MaxLength(1), MinLength(1)]
public JobTask[] TaskDescriptions { get; set; }
}
Upvotes: 4
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
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
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
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