Jak korzystać z IValidatableObject?

Rozumiem, że IValidatableObject jest używany do walidacji obiektu w sposób, który porównuje właściwości ze sobą.

Nadal chciałbym mieć atrybuty do walidacji poszczególnych właściwości, ale w niektórych przypadkach chcę ignorować błędy niektórych właściwości.

Czy próbuję użyć go nieprawidłowo w poniższym przypadku? Jeśli nie, to jak to zaimplementować?

public class ValidateMe : IValidatableObject
{
[Required]
public bool Enable { get; set; }

[Range(1, 5)]
public int Prop1 { get; set; }

[Range(1, 5)]
public int Prop2 { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if (!this.Enable)
    {
        /* Return valid result here.
         * I don't care if Prop1 and Prop2 are out of range
         * if the whole object is not "enabled"
         */
    }
    else
    {
        /* Check if Prop1 and Prop2 meet their range requirements here
         * and return accordingly.
         */ 
    }
}
}
Author: MatthewMartin, 2010-08-04

6 answers

Po pierwsze, dzięki @ paper1337 za wskazanie mi odpowiednich zasobów...Nie jestem zarejestrowany, więc nie mogę na niego zagłosować, proszę to zrobić, jeśli ktoś jeszcze to przeczyta.

Oto, jak osiągnąć to, co próbowałem zrobić.

Klasa Walidacyjna:

public class ValidateMe : IValidatableObject
{
    [Required]
    public bool Enable { get; set; }

    [Range(1, 5)]
    public int Prop1 { get; set; }

    [Range(1, 5)]
    public int Prop2 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();
        if (this.Enable)
        {
            Validator.TryValidateProperty(this.Prop1,
                new ValidationContext(this, null, null) { MemberName = "Prop1" },
                results);
            Validator.TryValidateProperty(this.Prop2,
                new ValidationContext(this, null, null) { MemberName = "Prop2" },
                results);

            // some other random test
            if (this.Prop1 > this.Prop2)
            {
                results.Add(new ValidationResult("Prop1 must be larger than Prop2"));
            }
        }
        return results;
    }
}

Użycie Validator.TryValidateProperty() doda do zbioru wyników, jeśli nie zostaną przeprowadzone walidacje. Jeśli nie ma nieudanej walidacji, nic nie zostanie dodane do kolekcji wyników, która jest oznaką sukcesu.

Doing the Walidacja:

    public void DoValidation()
    {
        var toValidate = new ValidateMe()
        {
            Enable = true,
            Prop1 = 1,
            Prop2 = 2
        };

        bool validateAllProperties = false;

        var results = new List<ValidationResult>();

        bool isValid = Validator.TryValidateObject(
            toValidate,
            new ValidationContext(toValidate, null, null),
            results,
            validateAllProperties);
    }

Ważne jest, aby ustawić validateAllProperties Na false, aby ta metoda działała. Gdy validateAllProperties jest false, sprawdzane są tylko właściwości z atrybutem [Required]. Pozwala to metodzie IValidatableObject.Validate() obsługiwać walidacje warunkowe.

 139
Author: zrg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 17:49:02

Cytat z bloga Jeffa Handleya na temat walidacji obiektów i właściwości za pomocą walidatora :

Podczas walidacji obiektu, następujący proces jest stosowany w Walidator.ValidateObject:

  1. Walidacja atrybutów na poziomie właściwości
  2. Jeśli walidatory są nieprawidłowe, przerwij walidację zwracając awarie
  3. sprawdzanie atrybutów na poziomie obiektu
  4. jeśli walidatory są nieprawidłowe, przerwij walidację zwracając awarie
  5. Jeśli na pulpicie framework i obiekt implementuje IValidatableObject, a następnie wywołać jego Validate method and return any failure(s)

Oznacza to, że to, co próbujesz zrobić, nie zadziała po wyjęciu z pudełka, ponieważ Walidacja zostanie przerwana w Kroku # 2. Możesz spróbować utworzyć atrybuty dziedziczące po wbudowanych i dokładnie sprawdzić obecność włączonej właściwości (poprzez interfejs) przed wykonując ich normalną walidację. Alternatywnie, możesz umieścić całą logikę walidacji encji w metodzie Validate.

 70
Author: Chris Shouts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-03 20:44:45

Aby dodać kilka punktów:

Ponieważ podpis metody Validate() zwraca IEnumerable<>, że yield return może być używany do leniwego generowania wyników - jest to korzystne, jeśli niektóre kontrole walidacji są obciążone IO lub CPU.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if (this.Enable)
    {
        // ...
        if (this.Prop1 > this.Prop2)
        {
            yield return new ValidationResult("Prop1 must be larger than Prop2");
        }

Ponadto, jeśli używasz MVC ModelState, możesz przekonwertować błędy wyniku walidacji na wpisy ModelState w następujący sposób (może to być przydatne, jeśli wykonujesz walidację w segregatorze niestandardowego modelu ):

var resultsGroupedByMembers = validationResults
    .SelectMany(vr => vr.MemberNames
                        .Select(mn => new { MemberName = mn ?? "", 
                                            Error = vr.ErrorMessage }))
    .GroupBy(x => x.MemberName);

foreach (var member in resultsGroupedByMembers)
{
    ModelState.AddModelError(
        member.Key,
        string.Join(". ", member.Select(m => m.Error)));
}
 27
Author: StuartLC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:12

Zaimplementowałem klasy abstrakcyjne ogólnego użycia dla walidacji

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace App.Abstractions
{
    [Serializable]
    abstract public class AEntity
    {
        public int Id { get; set; }

        public IEnumerable<ValidationResult> Validate()
        {
            var vResults = new List<ValidationResult>();

            var vc = new ValidationContext(
                instance: this,
                serviceProvider: null,
                items: null);

            var isValid = Validator.TryValidateObject(
                instance: vc.ObjectInstance,
                validationContext: vc,
                validationResults: vResults,
                validateAllProperties: true);

            /*
            if (true)
            {
                yield return new ValidationResult("Custom Validation","A Property Name string (optional)");
            }
            */

            if (!isValid)
            {
                foreach (var validationResult in vResults)
                {
                    yield return validationResult;
                }
            }

            yield break;
        }


    }
}
 3
Author: guneysus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-20 23:25:23

Problem z zaakceptowaną odpowiedzią polega na tym, że od wywołującego zależy, czy obiekt zostanie poprawnie zweryfikowany. Chciałbym albo usunąć RangeAttribute i zrobić walidację zakresu wewnątrz metody Validate lub chciałbym utworzyć niestandardowy atrybut podklasowania RangeAttribute, który bierze nazwę wymaganej właściwości jako argument konstruktora.

Na przykład:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class RangeIfTrueAttribute : RangeAttribute
{
    private readonly string _NameOfBoolProp;

    public RangeIfTrueAttribute(string nameOfBoolProp, int min, int max) : base(min, max)
    {
        _NameOfBoolProp = nameOfBoolProp;
    }

    public RangeIfTrueAttribute(string nameOfBoolProp, double min, double max) : base(min, max)
    {
        _NameOfBoolProp = nameOfBoolProp;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_NameOfBoolProp);
        if (property == null)
            return new ValidationResult($"{_NameOfBoolProp} not found");

        var boolVal = property.GetValue(validationContext.ObjectInstance, null);

        if (boolVal == null || boolVal.GetType() != typeof(bool))
            return new ValidationResult($"{_NameOfBoolProp} not boolean");

        if ((bool)boolVal)
        {
            return base.IsValid(value, validationContext);
        }
        return null;
    }
}
 0
Author: cocogza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-28 22:10:04

Podobała mi się odpowiedź cocogzy z wyjątkiem tej bazy połączeń.IsValid wywołał wyjątek przepełnienia stosu, ponieważ ponownie wprowadzał metodę IsValid. Więc zmodyfikowałem go do określonego rodzaju walidacji, w moim przypadku było to dla adresu e-mail.

[AttributeUsage(AttributeTargets.Property)]
class ValidEmailAddressIfTrueAttribute : ValidationAttribute
{
    private readonly string _nameOfBoolProp;

    public ValidEmailAddressIfTrueAttribute(string nameOfBoolProp)
    {
        _nameOfBoolProp = nameOfBoolProp;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            return null;
        }

        var property = validationContext.ObjectType.GetProperty(_nameOfBoolProp);
        if (property == null)
        {
            return new ValidationResult($"{_nameOfBoolProp} not found");
        }

        var boolVal = property.GetValue(validationContext.ObjectInstance, null);

        if (boolVal == null || boolVal.GetType() != typeof(bool))
        {
            return new ValidationResult($"{_nameOfBoolProp} not boolean");
        }

        if ((bool)boolVal)
        {
            var attribute = new EmailAddressAttribute {ErrorMessage = $"{value} is not a valid e-mail address."};
            return attribute.GetValidationResult(value, validationContext);
        }
        return null;
    }
}
To działa znacznie lepiej! Nie zawiesza się i generuje ładny komunikat o błędzie. Mam nadzieję, że to komuś pomoże!
 0
Author: rjacobsen0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 17:55:53