IT박스

IValidatableObject를 어떻게 사용합니까?

itboxs 2020. 6. 2. 19:02
반응형

IValidatableObject를 어떻게 사용합니까?


나는 이해 IValidatableObject하자 하나가 서로에 대한 특성을 비교하는 방법으로 개체를 확인하는 데 사용됩니다.

개별 속성의 유효성을 검사하는 특성을 계속 갖고 싶지만 특정 경우에 일부 속성의 실패를 무시하고 싶습니다.

아래의 경우에 잘못 사용하려고합니까? 그렇지 않으면 어떻게 구현합니까?

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.
             */ 
        }
    }
}

우선, 올바른 리소스를 알려주는 @ paper1337 덕분에 ... 나는 등록 할 수 없으므로 투표 할 수 없습니다. 다른 사람이 이것을 읽으면 그렇게하십시오.

내가하려는 일을 성취하는 방법은 다음과 같습니다.

유효한 클래스 :

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

Validator.TryValidateProperty()유효성 검사에 실패하면를 사용 하면 결과 컬렉션에 추가됩니다. 실패한 유효성 검사가 없으면 성공을 나타내는 결과 컬렉션에 추가되지 않습니다.

유효성 검사하기 :

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

validateAllProperties이 방법이 작동하려면 false 로 설정 해야합니다. validateAllProperties인과 거짓 전용 속성 [Required]속성이 확인됩니다. 이를 통해 IValidatableObject.Validate()메소드가 조건부 유효성 검증을 처리 할 수 있습니다 .


유효성 검사기를 사용하여 유효성 검사 개체 및 속성에 대한 Jeff Handley의 블로그 게시물에서 인용 :

객체의 유효성을 검사 할 때 Validator.ValidateObject에 다음 프로세스가 적용됩니다.

  1. 특성 레벨 속성 검증
  2. 유효성 검사기가 유효하지 않은 경우 유효성 검사를 중단하여 실패를 반환합니다.
  3. 객체 레벨 속성 검증
  4. 유효성 검사기가 유효하지 않은 경우 유효성 검사를 중단하여 실패를 반환합니다.
  5. 데스크탑 프레임 워크에서 오브젝트가 IValidatableObject를 구현하는 경우 Validate 메소드를 호출하고 실패를 리턴하십시오.

이는 2 단계에서 유효성 검사가 중단되므로 시도하려는 작업이 기본적으로 작동하지 않음을 나타냅니다. 기본 유효성 검사를 수행하기 전에 기본 제공 속성에서 상속되는 속성을 만들고 인터페이스를 통해 활성화 된 속성이 있는지 확인하십시오. 또는 Validate메소드 에서 엔티티 유효성 검증을위한 모든 로직을 넣을 수 있습니다.


몇 가지 사항을 추가하면됩니다.

때문에 Validate()메서드 서명 반환 IEnumerable<>, yield return유유히 결과를 생성하는 데 사용할 수 있습니다 -이 도움이되는 유효성 검사 중 일부는 IO 또는 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");
        }

Also, if you are using MVC ModelState, you can convert the validation result failures to ModelState entries as follows (this might be useful if you are doing the validation in a custom model binder):

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

I implemented a general usage abstract class for validation

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


    }
}

The problem with the accepted answer is that it now depends on the caller for the object to be properly validated. I would either remove the RangeAttribute and do the range validation inside the Validate method or I would create a custom attribute subclassing RangeAttribute that takes the name of the required property as an argument on the constructor.

For example:

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

I liked cocogza's answer except that calling base.IsValid resulted in a stack overflow exception as it would re-enter the IsValid method again and again. So I modified it to be for a specific type of validation, in my case it was for an e-mail address.

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

This works much better! It doesn't crash and produces a nice error message. Hope this helps someone!

참고URL : https://stackoverflow.com/questions/3400542/how-do-i-use-ivalidatableobject

반응형