Validación de ViewModel para una lista

76

Tengo la siguiente definición de modelo de vista

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

Entonces, en mi solicitud debe haber al menos 1 persona para una solicitud de acceso. ¿Qué enfoque podría utilizar para validar? No quiero que esta validación ocurra en mi controlador, lo cual 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");
Ryan
fuente

Respuestas:

172

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<Person> 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<Person> 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");
Darin Dimitrov
fuente
Parece que necesito usar la sobrecarga Must () para usar personas. Contar, por favor vea mi edición y avíseme si tiene una versión que sea más amigable :)
Ryan
1
@ryan, de hecho hay dos sobrecargas de este método como se muestra en la documentación . Entonces mi versión es más amigable. No se preocupe si Visual Studio lo subraya como un error. Debería funcionar si intenta compilar. Es solo que VS Intellisense no es lo suficientemente avanzado para entenderlo :-) Por lo que RuleFor(x => x.Persons).Must(x => x.Count > 0).WithMessage("At least a person is required");se compilará y funcionará bien.
Darin Dimitrov
Extraño, ahora no está subrayando. ¡Gracias!
Ryan
Gracias, Darin, eligió un nombre de atributo genial (: Solo tengo curiosidad si está utilizando algún marco de validación para el lado del cliente también. Las anotaciones de datos integradas brindan la ventaja del lado del cliente si se encuentra útil.
Saro Taşciyan
¿Cómo debo usarlo en la maquinilla de afeitar? Si escribo @foreach (var p en Model.Person) {...} @ Html.ValidationMessageFor (model => Model.Person) se llama al método del servidor de validación pero no se muestra el mensaje de validación.
Kate
18

Otra forma posible de manejar las validaciones de recuento para los miembros de la colección del objeto del modelo de vista es tener una propiedad calculada que devuelva la colección o el recuento de la lista. Luego, se puede aplicar un RangeAttribute como en el código siguiente para hacer cumplir la validación del 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 está validando, y Items es una propiedad de colección de miembros de ejemplo cuyo recuento se está comprobando. En este ejemplo, al menos un elemento se aplica al miembro de la colección y el límite máximo es el valor máximo que puede tomar un número entero, que es, para la mayoría de los propósitos prácticos, ilimitado. El mensaje de error en caso de error de validación también se puede establecer a través del miembro ErrorMessage de RangeAttribute en el ejemplo anterior.

Sudhir
fuente
14

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<WorkOrderItem> Items { get; set; }
Rahulmohan
fuente
1
Parece que esto ya no funciona en .NET Core 2.1.0 (Preview 1)
Sven
3
Esto funciona en .Net Core 2.2, lo he intentado en un tipo de lista. Si desea un mensaje personalizado separado para el atributo [Obligatorio], también es posible. [Requerido (ErrorMessage = "Metros faltantes"), MinLength (1, ErrorMessage = "Se requiere al menos 1 metro")] Lista pública <Meter> Metros {get; conjunto; }
Jeshwel
8

La respuesta de Darin es buena, pero la versión siguiente 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<Customer> Customers {get;set}

[MinimumElements(2)]
public List<Address> Addresses {get;set}

Mensaje de error:

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

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 IValidatableObjectinterfaz (que define un Validatemétodo)

Espero que esto ayude :)

AbdouMoumen
fuente
0

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<Person> Persons { get; private set; }
}

O [MinimumItems(1)].

goenning
fuente
0

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

public class AccessRequestViewModel
{
    private AccessRequesetViewModel() { };

    public static GetAccessRequestViewModel (List<Person> persons)
    {
            return new AccessRequestViewModel()
            {
                Persons = persons,
            };
    }

    public Request Request { get; private set; }
    public SelectList Buildings { get; private set; }
    public List<Person> Persons { get; private set; }
}

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

Probablemente esto no sea ideal para lo que desea, pero probablemente funcionaría.

Ian P
fuente