IValidatableObject를 어떻게 사용합니까?


182

나는 이해 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.
             */ 
        }
    }
}

답변:


168

우선, 올바른 리소스를 알려주는 @ 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()메소드가 조건부 유효성 검증을 처리 할 수 있습니다 .


나는 이것을 사용할 시나리오를 생각할 수 없다. 이것을 어디에서 사용할지 예를 들어 주시겠습니까?
Stefan Vasiljevic

테이블에 추적 열이있는 경우 (예 : 테이블을 만든 사용자 등) 데이터베이스에는 필요하지만 컨텍스트에서 SaveChanges를 입력하여 데이터베이스를 채우십시오 (개발자가 명시 적으로 설정해야하는 것을 기억하지 않아도 됨). 물론 저장하기 전에 유효성을 검사해야합니다. 따라서 "작성자"열을 필수로 표시하지 않고 다른 모든 열 / 속성에 대해 유효성을 검사하십시오.
MetalPhoenix

이 솔루션의 문제점은 이제 개체의 유효성을 검사하기 위해 호출자에 의존한다는 것입니다.
cocogza

이 답변을 향상시키기 위해 리플렉션을 사용하여 유효성 검사 속성이있는 모든 속성을 찾은 다음 TryValidateProperty를 호출 할 수 있습니다.
Paul Chernoch

78

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

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

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

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


36

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

때문에 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");
        }

또한를 사용 MVC ModelState하는 경우 유효성 검사 결과 실패를 ModelState다음과 같이 항목으로 변환 할 수 있습니다 ( 사용자 정의 모델 바인더 에서 유효성 검증을 수행하는 경우 유용 할 수 있음 ).

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

좋은 것! Validate 메소드에서 속성과 리플렉션을 사용하는 것이 가치가 있습니까?
Schalk

4

유효성 검사를 위해 일반 사용 추상 클래스를 구현했습니다.

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


    }
}

1
나는 명명 된 매개 변수를 사용하는 스타일을 좋아하여 코드를 훨씬 쉽게 읽을 수 있습니다.
drizin

0

허용되는 대답의 문제점은 이제 개체의 유효성을 검사하기위한 호출자에 달려 있다는 것입니다. RangeAttribute를 제거하고 Validate 메서드 내에서 범위 유효성 검사를 수행하거나 필요한 속성의 이름을 생성자의 인수로 사용하는 RangeAttribute를 하위 클래스로 만드는 사용자 지정 특성을 만듭니다.

예를 들면 다음과 같습니다.

[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

호출 base.IsValid를 제외하고 cocogza의 대답이 마음에 들었습니다 .IsValid 는 IsValid 메서드를 반복해서 다시 입력 할 때 스택 오버플로 예외가 발생했습니다. 그래서 특정 유형의 유효성 검사를 위해 수정했습니다. 필자의 경우 전자 메일 주소였습니다.

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

이것은 훨씬 잘 작동합니다! 충돌하지 않고 멋진 오류 메시지를 생성합니다. 이것이 누군가를 돕기를 바랍니다!


0

내가 iValidate에 대해 싫어하는 것은 다른 모든 유효성 검사 후에 만 ​​실행되는 것 같습니다.
또한 적어도 우리 사이트에서는 저장 시도 중에 다시 실행됩니다. 간단히 함수를 만들고 모든 유효성 검사 코드를 그 안에 넣는 것이 좋습니다. 또는 웹 사이트의 경우 모델을 만든 후 컨트롤러에서 "특별한"유효성 검사를 수행 할 수 있습니다. 예:

 public ActionResult Update([DataSourceRequest] DataSourceRequest request, [Bind(Exclude = "Terminal")] Driver driver)
    {

        if (db.Drivers.Where(m => m.IDNumber == driver.IDNumber && m.ID != driver.ID).Any())
        {
            ModelState.AddModelError("Update", string.Format("ID # '{0}' is already in use", driver.IDNumber));
        }
        if (db.Drivers.Where(d => d.CarrierID == driver.CarrierID
                                && d.FirstName.Equals(driver.FirstName, StringComparison.CurrentCultureIgnoreCase)
                                && d.LastName.Equals(driver.LastName, StringComparison.CurrentCultureIgnoreCase)
                                && (driver.ID == 0 || d.ID != driver.ID)).Any())
        {
            ModelState.AddModelError("Update", "Driver already exists for this carrier");
        }

        if (ModelState.IsValid)
        {
            try
            {
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.