ASP.NET MVC: Benutzerdefinierte Validierung durch DataAnnotation

110

Ich habe ein Modell mit 4 Eigenschaften vom Typ Zeichenfolge. Ich weiß, dass Sie die Länge einer einzelnen Eigenschaft mithilfe der StringLength-Annotation überprüfen können. Ich möchte jedoch die Länge der 4 kombinierten Eigenschaften überprüfen.

Wie kann MVC dies mit Datenanmerkungen tun?

Ich frage dies, weil ich neu bei MVC bin und es richtig machen möchte, bevor ich meine eigene Lösung mache.

Danny van der Kraan
quelle
2
Haben Sie sich Fluent Validation angesehen? Es behandelt komplexe Szenarien viel besser als
Datenanmerkungen
Werfen
Niks
Danke für die Antwort. Ich werde Fluent Validation ausprobieren, noch nie davon gehört. Und Niks, Darin hat im Grunde geschrieben, was der Artikel unter dem von Ihnen geposteten Link erklärt hat. Also, danke ... Super Zeug!
Danny van der Kraan

Antworten:

177

Sie können ein benutzerdefiniertes Validierungsattribut schreiben:

public class CombinedMinLengthAttribute: ValidationAttribute
{
    public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
        this.MinLength = minLength;
    }

    public string[] PropertyNames { get; private set; }
    public int MinLength { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
        var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
        var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
        if (totalLength < this.MinLength)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }
}

und dann könnten Sie ein Ansichtsmodell haben und eine seiner Eigenschaften damit dekorieren:

public class MyViewModel
{
    [CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
    public string Foo { get; set; }
    public string Bar { get; set; }
    public string Baz { get; set; }
}
Darin Dimitrov
quelle
4
Vielen Dank für Ihre Antwort. Ich habe Ihre Antwort akzeptiert. Fühle mich eigentlich ein bisschen verlegen. Sie haben die gesamte Lösung ausgeschrieben! Hehe. Musste nur die IsValid-Funktion ändern, um die maximale Länge zu überprüfen. Ist dies die akzeptierte MVC-Lösung für diese Art von Problemen?
Danny van der Kraan
7
@DannyvanderKraan, ja, das ist der akzeptierte Weg. Das ist natürlich so schlimm, dass ich es nie benutze und stattdessen FluentValidation.NET verwende, um die Validierung durchzuführen.
Darin Dimitrov
11
Hier: fluentvalidation.codeplex.com . Sie hätten einfach einen einfachen Validator für das Ansichtsmodell schreiben können, der möglicherweise so ausgesehen hat (eine einzelne Codezeile) : this.RuleFor(x => x.Foo).Must((x, foo) => x.Foo.Length + x.Bar.Length + x.Baz.Length < 20).WithMessage("The combined minimum length of the Foo, Bar and Baz properties should be longer than 20");. Schauen Sie sich nun den Code in meiner Antwort an, den Sie mit den Datenanmerkungen schreiben müssen, und sagen Sie mir, welchen Sie bevorzugen. Das deklarative Validierungsmodell ist im Vergleich zu einem imperativen Modell sehr schlecht.
Darin Dimitrov
1
Dies ist etwas spät, aber weiß jemand, ob es eine andere Einstellung gibt, die Sie "aktivieren" müssen, um benutzerdefinierte Datenanmerkungen zuzulassen? Ich weiß, wie man einen Namespace für unauffällige js in der Datei web.config hinzufügt, aber irgendwo anders?
Jose
1
Ich habe auf der Suche nach diesem ganzen Morgen! Ich habe es implementiert, und wenn IsValides heißt, ist das leider validationContextnull. Irgendeine Idee, was ich falsch gemacht habe? :-(
Grimm The Opiner
95

Selbst validiertes Modell

Ihr Modell sollte eine Schnittstelle implementieren IValidatableObject. Geben Sie Ihren Validierungscode in die folgende ValidateMethode ein:

public class MyModel : IValidatableObject
{
    public string Title { get; set; }
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Title == null)
            yield return new ValidationResult("*", new [] { nameof(Title) });

        if (Description == null)
            yield return new ValidationResult("*", new [] { nameof(Description) });
    }
}

Bitte beachten Sie: Dies ist eine serverseitige Validierung. Auf Client-Seite funktioniert es nicht. Ihre Validierung wird erst nach dem Absenden des Formulars durchgeführt.

Andrei
quelle
Danke, dass du Andrei geantwortet hast. Während Ihre Lösung auch funktionieren würde, wähle ich Darins, weil es wiederverwendbarer ist.
Danny van der Kraan
6
Yield Return New ValidationResult ("Der Titel ist obligatorisch.", "Titel"); würde den Eigenschaftsnamen hinzufügen, der nützlich ist, um Validierungsfehler für die Anzeige zu gruppieren, falls erforderlich.
Mike Kingscott
5
Beachten Sie, dass diese Validierungsmethode erst aufgerufen wird, nachdem alle Validierungsattribute die Validierung bestanden haben.
Pedro
3
Dies funktionierte gut für mich, da meine Validierung sehr spezifisch war. Das Hinzufügen eines benutzerdefinierten Attributs wäre für mich übertrieben gewesen, da die Validierung nicht wiederverwendet werden würde.
Steve S
Das suche ich. Danke dir!
Amol Jadhav
26

ExpressiveAnnotations bietet Ihnen eine solche Möglichkeit:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
jwaliszko
quelle
Das ist brilliant! Meine Gebete wurden beantwortet :)
Korayem
Ich habe gerade diese Antwort gefunden und es hat einfach viel Zeit gespart. ExpressiveAnnotations sind brillant!
Brad
10

Um Darins Antwort zu verbessern, kann sie etwas kürzer sein:

public class UniqueFileName : ValidationAttribute
{
    private readonly NewsService _newsService = new NewsService();

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        var file = (HttpPostedFile) value;

        return _newsService.IsFileNameUnique(file.FileName);
    }
}

Modell:

[UniqueFileName(ErrorMessage = "This file name is not unique.")]

Beachten Sie, dass eine Fehlermeldung erforderlich ist, da sonst der Fehler leer ist.

Jamie
quelle
8

Hintergrund:

Modellvalidierungen sind erforderlich, um sicherzustellen, dass die empfangenen Daten, die wir erhalten, gültig und korrekt sind, damit wir die weitere Verarbeitung mit diesen Daten durchführen können. Wir können ein Modell in einer Aktionsmethode validieren. Die integrierten Validierungsattribute sind Compare, Range, RegularExpression, Required, StringLength. Möglicherweise haben wir jedoch Szenarien, in denen wir andere als die integrierten Validierungsattribute benötigen.

Benutzerdefinierte Validierungsattribute

public class EmployeeModel 
{
    [Required]
    [UniqueEmailAddress]
    public string EmailAddress {get;set;}
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public int OrganizationId {get;set;}
}

Um ein benutzerdefiniertes Validierungsattribut zu erstellen, müssen Sie diese Klasse von ValidationAttribute ableiten.

public class UniqueEmailAddress : ValidationAttribute
{
    private IEmployeeRepository _employeeRepository;
    [Inject]
    public IEmployeeRepository EmployeeRepository
    {
        get { return _employeeRepository; }
        set
        {
            _employeeRepository = value;
        }
    }
    protected override ValidationResult IsValid(object value,
                        ValidationContext validationContext)
    {
        var model = (EmployeeModel)validationContext.ObjectInstance;
        if(model.Field1 == null){
            return new ValidationResult("Field1 is null");
        }
        if(model.Field2 == null){
            return new ValidationResult("Field2 is null");
        }
        if(model.Field3 == null){
            return new ValidationResult("Field3 is null");
        }
        return ValidationResult.Success;
    }
}

Hoffe das hilft. Prost !

Verweise

Yasser Shaikh
quelle
1

Ein bisschen spät zu antworten, aber für wen sucht. Sie können dies einfach tun, indem Sie eine zusätzliche Eigenschaft mit der Datenanmerkung verwenden:

public string foo { get; set; }
public string bar { get; set; }

[MinLength(20, ErrorMessage = "too short")]
public string foobar 
{ 
    get
    {
        return foo + bar;
    }
}

Das ist alles, was es wirklich ist. Wenn Sie den Validierungsfehler wirklich auch an einer bestimmten Stelle anzeigen möchten, können Sie diesen in Ihrer Ansicht hinzufügen:

@Html.ValidationMessage("foobar", "your combined text is too short")

Dies in der Ansicht zu tun, kann nützlich sein, wenn Sie eine Lokalisierung durchführen möchten.

Hoffe das hilft!

Leo Müller
quelle