Validación de ViewModel para una lista

Tengo la siguiente definición de viewmodel

public class AccessRequestViewModel { public Request Request { get; private set; } public SelectList Buildings { get; private set; } public List Persons { get; private set; } } 

Entonces en mi aplicación debe haber al menos 1 persona para una solicitud de acceso. ¿Qué enfoque usarías para validar? No quiero que esta validación suceda en mi controlador, que sería simple de hacer. ¿La única opción es un atributo de validación personalizado?

Editar: Actualmente realizando esta validación con FluentValidation (¡buena biblioteca!)

 RuleFor(vm => vm.Persons) .Must((vm, person) => person.Count > 0) .WithMessage("At least one person is required"); 

Si está utilizando anotaciones de datos para realizar la validación, es posible que necesite un atributo personalizado:

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

y entonces:

 [EnsureOneElement(ErrorMessage = "At least a person is required")] public List Persons { get; private set; } 

o para hacerlo más genérico:

 public class EnsureMinimumElementsAttribute : ValidationAttribute { private readonly int _minElements; public EnsureMinimumElementsAttribute(int minElements) { _minElements = minElements; } public override bool IsValid(object value) { var list = value as IList; if (list != null) { return list.Count >= _minElements; } return false; } } 

y entonces:

 [EnsureMinimumElements(1, ErrorMessage = "At least a person is required")] public List Persons { get; private set; } 

Personalmente uso FluentValidation.NET en lugar de anotaciones de datos para realizar la validación porque prefiero la lógica de validación imperativa en lugar de la declarativa. Creo que es más poderoso. Entonces mi regla de validación simplemente se vería así:

 RuleFor(x => x.Persons) .Must(x => x.Count > 0) .WithMessage("At least a person is required"); 

Otra forma posible de manejar las validaciones de recuento para los miembros de la colección del objeto de modelo de vista es tener una propiedad calculada que devuelva el recuento de la colección o la lista. A continuación, se puede aplicar un RangeAttribute como en el siguiente código para imponer la validación de recuento:

 [Range(minimum: 1, maximum: Int32.MaxValue, ErrorMessage = "At least one item needs to be selected")] public int ItemCount { get { return Items != null ? Items.Length : 0; } } 

En el código anterior, ItemCount es una propiedad calculada de ejemplo en un modelo de vista que se valida, y Items es una propiedad de colección de miembros de ejemplo cuyo conteo se está verificando. En este ejemplo, al menos un elemento se impone en el miembro de la colección y el límite máximo es el valor máximo que un entero puede tomar, que, para la mayoría de los propósitos prácticos, no tiene límites. El mensaje de error sobre la falla de validación también se puede establecer a través del miembro ErrorMessage de RangeAttribute en el ejemplo anterior.

La respuesta de Darin es buena, pero la siguiente versión le dará automáticamente un mensaje de error útil.

 public class MinimumElementsAttribute : ValidationAttribute { private readonly int minElements; public MinimumElementsAttribute(int minElements) { this.minElements = minElements; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var list = value as IList; var result = list?.Count >= minElements; return result ? ValidationResult.Success : new ValidationResult($"{validationContext.DisplayName} requires at least {minElements} element" + (minElements > 1 ? "s" : string.Empty)); } } 

Uso:

  [MinimumElements(1)] public List Customers {get;set} [MinimumElements(2)] public List
Addresses {get;set}

Mensaje de error:

  • Los clientes requieren al menos 1 elemento
  • Las direcciones requieren al menos 2 elementos

Aquí tiene dos opciones: crear un Atributo de validación personalizado y decorar la propiedad con él, o puede hacer que su ViewModel implemente la interfaz IValidatableObject (que define un método Validate )

Espero que esto ayude 🙂

El siguiente código funciona en asp.net core 1.1.

 [Required, MinLength(1, ErrorMessage = "At least one item required in work order")] public ICollection Items { get; set; } 

Sería muy limpio y elegante tener una validación personalizada. Algo como esto:

 public class AccessRequestViewModel { public Request Request { get; private set; } public SelectList Buildings { get; private set; } [AtLeastOneItem] public List Persons { get; private set; } } 

O [MinimumItems(1)] .

Un enfoque podría ser usar un constructor privado y un método estático para devolver una instancia del objeto.

 public class AccessRequestViewModel { private AccessRequesetViewModel() { }; public static GetAccessRequestViewModel (List persons) { return new AccessRequestViewModel() { Persons = persons, }; } public Request Request { get; private set; } public SelectList Buildings { get; private set; } public List Persons { get; private set; } } 

Al utilizar siempre la fábrica para crear una instancia de su ViewModel, puede asegurarse de que siempre habrá una persona.

Esto probablemente no es ideal para lo que quiere, pero probablemente funcionaría.