ViewModel-Validierung für eine Liste

76

Ich habe die folgende Ansichtsmodelldefinition

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

In meiner Bewerbung muss also mindestens 1 Person für eine Zugangsanfrage vorhanden sein. Welchen Ansatz könnten Sie zur Validierung verwenden? Ich möchte nicht, dass diese Validierung in meinem Controller erfolgt, was einfach zu bewerkstelligen wäre. Ist die einzige Auswahl ein benutzerdefiniertes Validierungsattribut?

Bearbeiten: Derzeit wird diese Validierung mit FluentValidation durchgeführt (nette Bibliothek!)

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

Antworten:

172

Wenn Sie Datenanmerkungen verwenden, um die Validierung durchzuführen, benötigen Sie möglicherweise ein benutzerdefiniertes Attribut:

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

und dann:

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

oder um es allgemeiner zu machen:

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;
    }
}

und dann:

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

Persönlich verwende ich FluentValidation.NET anstelle von Datenanmerkungen , um die Validierung durchzuführen, da ich die imperative Validierungslogik anstelle der deklarativen bevorzuge. Ich denke, es ist mächtiger. Meine Validierungsregel würde also einfach so aussehen:

RuleFor(x => x.Persons)
    .Must(x => x.Count > 0)
    .WithMessage("At least a person is required");
Darin Dimitrov
quelle
Es sieht so aus, als müsste ich die Überladung Must () verwenden, um Personen zu verwenden. Anzahl, bitte sehen Sie meine Bearbeitung und lassen Sie mich wissen, wenn Sie eine Version haben, die freundlicher ist :)
Ryan
1
@ryan, tatsächlich gibt es zwei Überladungen dieser Methode, wie in der Dokumentation gezeigt . Meine Version ist also freundlicher. Machen Sie sich keine Sorgen, wenn Visual Studio dies als Fehler unterstreicht. Es sollte funktionieren, wenn Sie versuchen zu kompilieren. Es ist nur so, dass VS Intellisense nicht weit genug fortgeschritten ist, um es zu verstehen :-) Also RuleFor(x => x.Persons).Must(x => x.Count > 0).WithMessage("At least a person is required");wird kompiliert und funktioniert gut.
Darin Dimitrov
Seltsam, jetzt wird es nicht unterstrichen. Vielen Dank!
Ryan
Vielen Dank Darin, nahm man einen kühlen Attributnamen (: Nur neugierig , wenn Sie irgendeine Validation Framework für verwenden clientseitige als auch Built-in. Daten Anmerkungen den Vorteil der Client - Seite zur Verfügung stellen , wenn man nützlich finden.
Saro Taşciyan
Wie soll ich es in Rasiermesser verwenden? Wenn ich @foreach schreibe (var p in Model.Person) {...} @ Html.ValidationMessageFor (model => Model.Person) wird die Validierungsservermethode aufgerufen, aber die Validierungsnachricht wird nicht angezeigt.
Kate
18

Eine andere Möglichkeit, die Anzahlüberprüfungen für die Sammlungsmitglieder des Ansichtsmodellobjekts durchzuführen, besteht darin, dass eine berechnete Eigenschaft die Anzahl der Sammlungen oder Listen zurückgibt. Ein RangeAttribute kann dann wie im folgenden Code angewendet werden, um die Überprüfung der Zählung zu erzwingen:

[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;
    }
}

Im obigen Code ist ItemCount eine berechnete Beispieleigenschaft für ein zu validierendes Ansichtsmodell, und Items ist eine beispielhafte Elementauflistungseigenschaft, deren Anzahl überprüft wird. In diesem Beispiel wird mindestens ein Element für das Sammlungsmitglied erzwungen, und die maximale Grenze ist der maximale Wert, den eine Ganzzahl annehmen kann, der für die meisten praktischen Zwecke unbegrenzt ist. Die Fehlermeldung bei einem Validierungsfehler kann auch über das ErrorMessage-Mitglied des RangeAttribute im obigen Beispiel festgelegt werden.

Sudhir
quelle
14

Der folgende Code funktioniert in asp.net Core 1.1.

[Required, MinLength(1, ErrorMessage = "At least one item required in work order")]
public ICollection<WorkOrderItem> Items { get; set; }
rahulmohan
quelle
1
Es scheint, dass dies in .NET Core 2.1.0 (Vorschau 1) nicht mehr funktioniert
Sven
3
Dies funktioniert in .Net Core 2.2. Ich habe dies bei einem Listentyp versucht. Wenn Sie eine separate benutzerdefinierte Nachricht für das Attribut [Erforderlich] wünschen, ist dies ebenfalls möglich. [Erforderlich (ErrorMessage = "Fehlende Meter"), MinLength (1, ErrorMessage = "Mindestens 1 Meter ist erforderlich")] public List <Meter> Meter {get; einstellen; }
Jeshwel
8

Darins Antwort ist gut, aber die folgende Version gibt Ihnen automatisch eine nützliche Fehlermeldung.

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));
    }
}

Verwendung:

[MinimumElements(1)]
public List<Customer> Customers {get;set}

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

Fehlermeldung:

  • Kunden benötigen mindestens 1 Element
  • Adressen erfordern mindestens 2 Elemente
Sam Shiles
quelle
2

Sie haben hier zwei Möglichkeiten: Erstellen Sie entweder ein benutzerdefiniertes Validierungsattribut und dekorieren Sie die Eigenschaft damit, oder Sie können Ihr ViewModel die IValidatableObjectSchnittstelle implementieren lassen (die eine ValidateMethode definiert ).

Hoffe das hilft :)

AbdouMoumen
quelle
0

Es wäre sehr sauber und elegant, eine benutzerdefinierte Validierung zu haben. Etwas wie das:

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

Oder [MinimumItems(1)].

goenning
quelle
0

Ein Ansatz könnte darin bestehen, einen privaten Konstruktor und eine statische Methode zu verwenden, um eine Instanz des Objekts zurückzugeben.

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; }
}

Indem Sie Ihr ViewModel immer über die Factory instanziieren, können Sie sicherstellen, dass immer eine Person anwesend ist.

Dies ist wahrscheinlich nicht ideal für das, was Sie wollen, aber es würde wahrscheinlich funktionieren.

Ian P.
quelle