ObservableValidator

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

플랫폼 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 도구 키트를 확인합니다.
  • 단위 테스트에서 더 많은 예제를 찾을 수도 있습니다.