ObservableValidator

인터페이스 ObservableValidator 를 구현하는 INotifyDataErrorInfo 기본 클래스로, 다른 애플리케이션 모듈에 노출된 속성의 유효성 검사를 지원합니다. 또한 ObservableObject을(를) 상속받으므로 INotifyPropertyChangedINotifyPropertyChanging도 구현합니다. 속성 변경 알림과 속성 유효성 검사를 모두 지원해야 하는 모든 종류의 개체에 대한 시작점으로 사용할 수 있습니다.

플랫폼 API:ObservableValidator, ObservableObject

작동 방식

ObservableValidator 에는 다음과 같은 주요 기능이 있습니다.

  • INotifyDataErrorInfo에 대한 기본 구현을 제공하며, ErrorsChanged 이벤트와 기타 필요한 API를 노출합니다.
  • 기본 SetProperty 클래스에서 제공하는 오버로드를 기반으로 하여 값을 업데이트하기 전에 속성의 유효성을 자동으로 검사하고 필요한 이벤트를 발생시키는 기능을 제공하는 일련의 추가 ObservableObject 오버로드를 제공합니다.
  • 이 메서드는 SetProperty와 유사하지만, 유효성 검사가 성공한 경우에만 대상 속성을 업데이트하고 추가로 확인할 수 있도록 생성된 오류(있는 경우)를 반환할 수 있는 여러 TrySetProperty 오버로드를 제공합니다.
  • 이 메서드는 ValidateProperty 해당 값이 업데이트되지 않았지만 유효성 검사가 대신 업데이트된 다른 속성의 값에 따라 달라지는 경우 특정 속성의 유효성 검사를 수동으로 트리거하는 데 유용할 수 있는 메서드를 노출합니다.
  • ValidateAllProperties 메서드를 제공하며, 이 메서드는 현재 인스턴스에서 [ValidationAttribute]이 하나 이상 적용된 모든 공개 인스턴스 속성의 유효성 검사를 자동으로 실행합니다.
  • 사용자가 다시 입력할 수 있는 폼에 바인딩된 모델을 재설정할 때 유용할 수 있는 ClearAllErrors 메서드를 제공합니다.
  • 다양한 매개 변수를 전달하여 속성의 유효성을 검사하는 데 사용할 인스턴스를 ValidationContext 초기화할 수 있는 여러 생성자를 제공합니다. 이는 추가 서비스 또는 옵션이 올바르게 작동해야 할 수 있는 사용자 지정 유효성 검사 특성을 사용할 때 특히 유용할 수 있습니다.

단순 속성

다음은 변경 알림과 유효성 검사를 모두 지원하는 속성을 구현하는 방법의 예입니다.

public class RegistrationForm : ObservableValidator
{
    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    public string Name
    {
        get => name;
        set => SetProperty(ref name, value, true);
    }
}

여기서는 ObservableValidator에 의해 노출된 SetProperty<T>(ref T, T, bool, string) 메서드를 호출하며, true(으)로 설정된 추가 bool 매개 변수는 해당 값이 업데이트될 때도 속성의 유효성을 검사하려는 것을 나타냅니다. ObservableValidator 는 속성에 적용된 특성과 함께 지정된 모든 검사를 사용하여 모든 새 값에 대한 유효성 검사를 자동으로 실행합니다. 그런 다음 다른 구성 요소(예: UI 컨트롤)는 viewmodel과 상호 작용하고 뷰모델에 현재 존재하는 오류를 반영하도록 해당 상태를 수정할 수 있습니다. 이 메서드를 등록하고 메서드를 ErrorsChanged 사용하여 GetErrors(string) 수정된 각 속성에 대한 오류 목록을 검색합니다.

사용자 지정 유효성 검사 메서드

속성의 유효성을 검사하려면 viewmodel이 추가 서비스, 데이터 또는 기타 API에 액세스할 수 있어야 하는 경우가 있습니다. 시나리오 및 필요한 유연성 수준에 따라 속성에 사용자 지정 유효성 검사를 추가하는 방법에는 여러 가지가 있습니다. 다음은 속성의 추가 유효성 검사를 수행하기 위해 특정 메서드를 호출해야 함을 나타내는 데 형식을 사용하는 방법 [CustomValidationAttribute] 의 예입니다.

public class RegistrationForm : ObservableValidator
{
    private readonly IFancyService service;

    public RegistrationForm(IFancyService service)
    {
        this.service = service;
    }

    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    [CustomValidation(typeof(RegistrationForm), nameof(ValidateName))]
    public string Name
    {
        get => this.name;
        set => SetProperty(ref this.name, value, true);
    }

    public static ValidationResult ValidateName(string name, ValidationContext context)
    {
        RegistrationForm instance = (RegistrationForm)context.ObjectInstance;
        bool isValid = instance.service.Validate(name);

        if (isValid)
        {
            return ValidationResult.Success;
        }

        return new("The name was not validated by the fancy service");
    }
}

이 경우 viewmodel에 삽입되는 서비스를 통해 속성에 대한 ValidateName 유효성 검사를 수행하는 정적 Name 메서드가 있습니다. 이 메서드는 name viewmodel 인스턴스, 유효성을 검사할 속성의 이름, 선택적으로 서비스 공급자 및 ValidationContext 사용하거나 설정할 수 있는 일부 사용자 지정 플래그와 같은 항목을 포함하는 사용 중인 인스턴스와 속성 값을 받습니다. 이 경우 유효성 검사 컨텍스트에서 인스턴스를 RegistrationForm 검색하고 여기에서 삽입된 서비스를 사용하여 속성의 유효성을 검사합니다. 이 유효성 검사는 다른 특성에 지정된 특성 옆에 실행되므로 사용자 지정 유효성 검사 메서드와 기존 유효성 검사 특성을 자유롭게 결합할 수 있습니다.

사용자 지정 유효성 검사 특성

사용자 지정 유효성 검사를 수행하는 또 다른 방법은 사용자 지정 [ValidationAttribute]을 구현한 다음 유효성 검사 로직을 재정의된 IsValid 메서드에 삽입하는 것입니다. 이렇게 하면 여러 위치에서 동일한 특성을 쉽게 재사용할 수 있으므로 위에서 설명한 접근 방식에 비해 유연성을 더할 수 있습니다.

동일한 viewmodel의 다른 속성과 관련하여 상대적 값을 기반으로 속성의 유효성을 검사하려고 했습니다. 첫 번째 단계는 다음과 같이 사용자 지정 [GreaterThanAttribute]을 정의하는 것입니다.

public sealed class GreaterThanAttribute : ValidationAttribute
{
    public GreaterThanAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    public string PropertyName { get; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        object
            instance = validationContext.ObjectInstance,
            otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);

        if (((IComparable)value).CompareTo(otherValue) > 0)
        {
            return ValidationResult.Success;
        }

        return new("The current value is smaller than the other one");
    }
}

다음으로 viewmodel에 이 특성을 추가할 수 있습니다.

public class ComparableModel : ObservableValidator
{
    private int a;

    [Range(10, 100)]
    [GreaterThan(nameof(B))]
    public int A
    {
        get => this.a;
        set => SetProperty(ref this.a, value, true);
    }

    private int b;

    [Range(20, 80)]
    public int B
    {
        get => this.b;
        set
        {
            SetProperty(ref this.b, value, true);
            ValidateProperty(A, nameof(A));
        }
    }
}

이 경우 특정 범위와 서로의 특정 관계(A 보다 B커야 합니다)에 있어야 하는 두 개의 숫자 속성이 있습니다. 첫 번째 속성 위에 새 [GreaterThanAttribute]를 추가했으며, B의 setter에 ValidateProperty 호출도 추가하여 B가 변경될 때마다 A가 다시 유효성 검사되도록 했습니다(A의 유효성 검사 상태가 B에 따라 달라지기 때문입니다). 이 사용자 지정 유효성 검사를 사용하도록 설정하려면 viewmodel에서 이러한 두 줄의 코드만 있으면 되며, 애플리케이션의 다른 viewmodel에서도 유용할 수 있는 재사용 가능한 사용자 지정 유효성 검사 특성이 있다는 이점이 있습니다. 또한 이 방법은 유효성 검사 논리가 viewmodel 정의 자체에서 완전히 분리되므로 코드 모듈화에도 도움이 됩니다.

예제

  • 샘플 앱(여러 UI 프레임워크의 경우)을 확인하여 작동 중인 MVVM 도구 키트를 확인합니다.
  • 단위 테스트에서 더 많은 예제를 찾을 수도 있습니다.