ASP.NET MVC: niestandardowa walidacja przez DataAnnotation

110

Mam Model z 4 właściwościami, które są typu string. Wiem, że możesz zweryfikować długość pojedynczej właściwości za pomocą adnotacji StringLength. Jednak chcę zweryfikować długość połączonych 4 właściwości.

W jaki sposób MVC to zrobić z adnotacją danych?

Pytam o to, ponieważ jestem nowy w MVC i chcę to zrobić we właściwy sposób przed stworzeniem własnego rozwiązania.

Danny van der Kraan
źródło
2
Czy spojrzałeś na Fluent Validation? Obsługuje złożone scenariusze znacznie lepiej niż adnotacje danych
levelnis
Zapoznaj się z wysoce zalecanymi rozwiązaniami ... dotnetcurry.com/ShowArticle.aspx?ID=776
Niks
Dzięki za odpowiedź. Sprawdzę Fluent Validation, nigdy o tym nie słyszałem. Niks, Darin w zasadzie napisał, co wyjaśnia artykuł pod linkiem, który zamieściłeś. Więc dziękuję ... Świetne rzeczy!
Danny van der Kraan

Odpowiedzi:

177

Możesz napisać niestandardowy atrybut walidacji:

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

a wtedy możesz mieć model widoku i ozdobić nim jedną z jego właściwości:

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
źródło
4
Dziękuję za odpowiedź, zaakceptowałem twoją odpowiedź. Właściwie czuję się trochę zawstydzony. Napisałeś całe rozwiązanie! Hehe. Trzeba było tylko zmienić funkcję IsValid, aby sprawdzić maksymalną długość. Czy jest to więc akceptowane rozwiązanie MVC dla tego typu problemów?
Danny van der Kraan
7
@DannyvanderKraan, tak, to jest przyjęty sposób. Oczywiście jest to tak straszne, że nigdy go nie używam i zamiast tego używam FluentValidation.NET do przeprowadzania walidacji.
Darin Dimitrov
11
Tutaj: fluentvalidation.codeplex.com . Mogłeś po prostu napisane prostą walidator dla modelu widoku, które mogły się następująco (w pojedynczej linii kodu) 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");. Teraz spójrz na kod w mojej odpowiedzi, że musisz napisać z adnotacjami danych i powiedz mi, który z nich wolisz. Deklaratywny model walidacji jest bardzo słaby w porównaniu z modelem imperatywnym.
Darin Dimitrov
1
To trochę za późno, ale czy ktoś wie, czy istnieje inne ustawienie, które trzeba „włączyć”, aby zezwolić na niestandardowe adnotacje z danymi? Wiem o dodawaniu przestrzeni nazw dla dyskretnego js w pliku web.config, ale gdziekolwiek indziej?
Jose
1
Szukałem tego cały ranek! Zaimplementowałem go i niestety, gdy IsValidnazywa się to validationContextjest null. Masz pojęcie, co zrobiłem źle? :-(
Grimm The Opiner
95

Model samodzielnie zweryfikowany

Twój model powinien implementować interfejs IValidatableObject. Umieść swój kod walidacyjny w Validatemetodzie:

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

Uwaga: to jest walidacja po stronie serwera . To nie działa po stronie klienta. Twoja walidacja zostanie przeprowadzona dopiero po przesłaniu formularza.

Andrei
źródło
Dzięki za odpowiedź Andrei. Chociaż twoje rozwiązanie też by działało, wybrałem Darin, ponieważ jest on bardziej wielokrotnego użytku.
Danny van der Kraan
6
yield return new ValidationResult ("Tytuł jest obowiązkowy.", "Tytuł"); dodałby nazwę właściwości, co jest przydatne w grupowaniu błędów walidacji do wyświetlenia w razie potrzeby.
Mike Kingscott
5
Należy zauważyć, że ta metoda walidacji jest wywoływana dopiero po przejściu wszystkich atrybutów walidacji.
Pedro
3
To działało dobrze, ponieważ moja walidacja była bardzo szczegółowa. Dodanie atrybutu niestandardowego byłoby dla mnie przesadą, ponieważ walidacja nie miałaby zostać ponownie wykorzystana.
Steve S,
To jest to, czego szukam. Dziękuję Ci!
Amol Jadhav
27

ExpressiveAnnotations daje Ci taką możliwość:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
jwaliszko
źródło
To jest genialne! moje modlitwy zostały wysłuchane :)
Korayem
Właśnie znalazłem tę odpowiedź i zaoszczędziłem mnóstwo czasu. Ekspresyjne Adnotacje są genialne!
Brad
10

Aby poprawić odpowiedź Darina, może być nieco krótsza:

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

Model:

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

Zwróć uwagę, że wymagany jest komunikat o błędzie, w przeciwnym razie błąd będzie pusty.

Jamie
źródło
8

Tło:

Walidacje modeli są wymagane w celu zapewnienia, że ​​otrzymane przez nas dane są ważne i poprawne, abyśmy mogli dalej przetwarzać te dane. Możemy zweryfikować model w metodzie akcji. Wbudowane atrybuty walidacji to Compare, Range, RegularExpression, Required, StringLength. Jednak możemy mieć scenariusze, w których wymagaliśmy atrybutów walidacji innych niż wbudowane.

Niestandardowe atrybuty walidacji

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

Aby utworzyć niestandardowy atrybut walidacji, musisz wyprowadzić tę klasę z ValidationAttribute.

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

Mam nadzieję że to pomoże. Twoje zdrowie !

Bibliografia

Yasser Shaikh
źródło
1

Trochę późno na odpowiedź, ale kto szuka. Możesz to łatwo zrobić, używając dodatkowej właściwości z adnotacją danych:

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

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

To wszystko, to też jest naprawdę. Jeśli naprawdę chcesz wyświetlić w określonym miejscu również błąd walidacji, możesz dodać to w swoim widoku:

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

zrobienie tego w widoku może się przydać, jeśli chcesz przeprowadzić lokalizację.

Mam nadzieję że to pomoże!

Leo Muller
źródło