다음을 통해 공유


Domain-Specific 언어의 유효성 검사

DSL(도메인별 언어)의 작성자로서 유효성 검사 제약 조건을 정의하여 사용자가 만든 모델이 의미가 있는지 확인할 수 있습니다. 예를 들어, DSL을 통해 사용자가 사람들과 그 조상의 가계도를 그릴 수 있는 경우, 자녀의 생년월일이 부모의 생년월일 이후임을 보장하는 제약 조건을 작성할 수 있습니다.

모델이 저장될 때, 모델을 열 때, 사용자가 명시적으로 유효성 검사 메뉴 명령을 실행할 때 유효성 검사 제약 조건을 실행할 수 있습니다. 프로그램 제어에서 유효성 검사를 실행할 수도 있습니다. 예를 들어 속성 값 또는 관계의 변경에 대한 응답으로 유효성 검사를 실행할 수 있습니다.

사용자의 모델을 처리하는 텍스트 템플릿 또는 기타 도구를 작성하는 경우 유효성 검사가 특히 중요합니다. 유효성 검사를 통해 모델은 해당 도구에서 가정한 사전 조건을 충족합니다.

경고

확장 메뉴 명령 및 제스처 처리기와 함께 DSL에 대한 별도의 확장에서 유효성 검사 제약 조건을 정의하도록 허용할 수도 있습니다. 사용자는 DSL 외에도 이러한 확장을 설치하도록 선택할 수 있습니다. 자세한 내용은 MEF를 사용하여 DSL 확장을 참조하세요.

유효성 검사 실행

사용자가 모델을 편집하는 경우, 즉 도메인별 언어의 인스턴스인 다음 작업은 유효성 검사를 실행할 수 있습니다.

  • 다이어그램을 마우스 오른쪽 단추로 클릭하고 모두 유효성 검사를 선택합니다.

  • DSL 탐색기에서 위쪽 노드를 마우스 오른쪽 단추로 클릭하고 모두 유효성 검사 선택

  • 모델을 저장합니다.

  • 모델을 엽니다.

  • 또한 메뉴 명령의 일부 또는 변경에 대한 응답으로 유효성 검사를 실행하는 프로그램 코드를 작성할 수 있습니다.

    모든 유효성 검사 오류가 오류 목록 창에 표시됩니다. 사용자는 오류 메시지를 두 번 클릭하여 오류의 원인인 모델 요소를 선택할 수 있습니다.

유효성 검사 제약 조건 정의

DSL의 도메인 클래스 또는 관계에 유효성 검사 메서드를 추가하여 유효성 검사 제약 조건을 정의합니다. 유효성 검사가 실행되면 사용자 또는 프로그램 제어에서 유효성 검사 메서드의 일부 또는 전부가 실행됩니다. 각 메서드는 해당 클래스의 각 인스턴스에 적용되며 각 클래스에 여러 유효성 검사 메서드가 있을 수 있습니다.

각 유효성 검사 메서드는 찾은 오류를 보고합니다.

비고

유효성 검사 메서드는 오류를 보고하지만 모델을 변경하지는 않습니다. 특정 변경 내용을 조정하거나 방지하려면 유효성 검사의 대안을 참조하세요.

유효성 검사 제약 조건을 정의하려면

  1. Editor\Validation 노드에서 유효성 검사를 사용하도록 설정합니다.

    1. Dsl\DslDefinition.dsl을 엽니다.

    2. DSL 탐색기에서 편집기 노드를 확장하고 유효성 검사를 선택합니다.

    3. 속성 창에서 사용 속성을 true.로 설정합니다. 이러한 모든 속성을 설정하는 것이 가장 편리합니다.

    4. 솔루션 탐색기 도구 모음에서 모든 템플릿 변환을 클릭합니다.

  2. 하나 이상의 도메인 클래스 또는 도메인 관계에 대한 부분 클래스 정의를 작성합니다. Dsl 프로젝트의 새 코드 파일에 이러한 정의를 작성합니다.

  3. 각 클래스에 다음 특성을 접두사로 지정합니다.

    [ValidationState(ValidationState.Enabled)]
    
    • 기본적으로 이 특성은 파생 클래스에 대한 유효성 검사를 사용하도록 설정합니다. 특정 파생 클래스에 대한 유효성 검사를 사용하지 않도록 설정하려면 ValidationState.Disabled를 사용하십시오.
  4. 클래스에 유효성 검사 메서드를 추가합니다. 각 유효성 검사 메서드는 이름을 가질 수 있지만 형식 ValidationContext의 매개 변수가 하나 있습니다.

    하나 이상의 ValidationMethod 특성을 접두사로 지정해야 합니다.

    [ValidationMethod (ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ]
    

    ValidationCategories는 메서드가 실행되는 시기를 지정합니다.

    다음은 그 예입니다.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;

// Allow validation methods in this class:
[ValidationState(ValidationState.Enabled)]
// In this DSL, ParentsHaveChildren is a domain relationship
// from Person to Person:
public partial class ParentsHaveChildren
{
  // Identify the method as a validation method:
  [ValidationMethod
  ( // Specify which events cause the method to be invoked:
    ValidationCategories.Open // On file load.
  | ValidationCategories.Save // On save to file.
  | ValidationCategories.Menu // On user menu command.
  )]
  // This method is applied to each instance of the
  // type (and its subtypes) in a model:
  private void ValidateParentBirth(ValidationContext context)
  {
    // In this DSL, the role names of this relationship
    // are "Child" and "Parent":
     if (this.Child.BirthYear < this.Parent.BirthYear
        // Allow user to leave the year unset:
        && this.Child.BirthYear != 0)
      {
        context.LogError(
             // Description:
                       "Child must be born after Parent",
             // Unique code for this error:
                       "FAB001ParentBirthError",
              // Objects to select when user double-clicks error:
                       this.Child,
                       this.Parent);
    }
  }

이 코드에 대한 다음 사항을 확인합니다.

  • 도메인 클래스 또는 도메인 관계에 유효성 검사 메서드를 추가할 수 있습니다. 이러한 형식에 대한 코드는 Dsl\Generated Code\Domain*.cs.

  • 각 유효성 검사 메서드는 해당 클래스의 모든 인스턴스와 해당 서브클래스에 적용됩니다. 도메인 관계의 경우 각 인스턴스는 두 모델 요소 간의 링크입니다.

  • 유효성 검사 메서드는 지정된 순서로 적용되지 않으며 각 메서드는 예측 가능한 순서로 해당 클래스의 인스턴스에 적용되지 않습니다.

  • 일반적으로 유효성 검사 방법으로 저장소 콘텐츠를 업데이트하는 것이 좋지 않습니다. 이로 인해 결과가 일관되지 않을 수 있기 때문입니다. 대신 context.LogError, LogWarning 또는 LogInfo를 호출하여 오류를 보고해야 합니다.

  • LogError 호출에서 사용자가 오류 메시지를 두 번 클릭할 때 선택될 모델 요소 또는 관계 링크 목록을 제공할 수 있습니다.

  • 프로그램 코드에서 모델을 읽는 방법에 대한 자세한 내용은 프로그램 코드에서 모델 탐색 및 업데이트를 참조하세요.

    이 예제는 다음 도메인 모델에 적용됩니다. ParentHaveChildren 관계에는 자식 및 부모라는 역할이 있습니다.

    DSL 정의 다이어그램 - 패밀리 트리 모델

유효성 검사 범주

특성에서 ValidationMethodAttribute 유효성 검사 메서드를 실행할 시기를 지정합니다.

카테고리 실행
ValidationCategories 사용자가 유효성 검사 메뉴 명령을 호출하는 경우
ValidationCategories 모델 파일을 열 때입니다.
ValidationCategories 파일이 저장되는 경우 유효성 검사 오류가 있는 경우 저장 작업을 취소하는 옵션이 사용자에게 제공됩니다.
ValidationCategories 파일이 저장되는 경우 이 범주의 메서드에서 오류가 발생하면 파일을 다시 열 수 없다는 경고가 사용자에게 표시됩니다.

중복된 이름 또는 ID를 테스트하는 유효성 검사 방법 또는 로드 오류를 일으킬 수 있는 기타 조건에 이 범주를 사용합니다.
ValidationCategories ValidateCustom 메서드가 호출되는 경우 이 범주의 유효성 검사는 프로그램 코드에서만 호출할 수 있습니다.

자세한 내용은 사용자 지정 유효성 검사 범주를 참조하세요.

유효성 검사 메서드를 배치할 위치

종종 다른 형식에 유효성 검사 메서드를 배치하여 동일한 효과를 얻을 수 있습니다. 예를 들어 ParentsHaveChildren 관계 대신 Person 클래스에 메서드를 추가하고 링크를 반복하도록 할 수 있습니다.

[ValidationState(ValidationState.Enabled)]
public partial class Person
{[ValidationMethod
 ( ValidationCategories.Open
 | ValidationCategories.Save
 | ValidationCategories.Menu
 )
]
  private void ValidateParentBirth(ValidationContext context)
  {
    // Iterate through ParentHasChildren links:
    foreach (Person parent in this.Parents)
    {
        if (this.BirthYear <= parent.BirthYear)
        { ...

유효성 검사 제약 조건 집계 예측 가능한 순서로 유효성 검사를 적용하려면 모델의 루트 요소와 같은 소유자 클래스에 단일 유효성 검사 메서드를 정의합니다. 이 기술을 사용하면 여러 오류 보고서를 단일 메시지로 집계할 수도 있습니다.

단점은 결합된 메서드가 관리하기 어렵고, 제약 조건이 모두 동일한 ValidationCategories를 가져야 한다는 것입니다. 따라서 가능한 경우 각 제약 조건을 별도의 메서드로 유지하는 것이 좋습니다.

컨텍스트 캐시에 값을 전달합니다. 컨텍스트 매개 변수에는 임의의 값을 배치할 수 있는 사전이 있습니다. 사전은 유효성 검사 실행의 수명 동안 유지됩니다. 예를 들어 특정 유효성 검사 메서드는 컨텍스트에서 오류 수를 유지하고 이를 사용하여 반복된 메시지로 오류 창이 쇄도하지 않도록 방지할 수 있습니다. 다음은 그 예입니다.

List<ParentsHaveChildren> erroneousLinks;
if (!context.TryGetCacheValue("erroneousLinks", out erroneousLinks))
erroneousLinks = new List<ParentsHaveChildren>();
erroneousLinks.Add(this);
context.SetCacheValue("erroneousLinks", erroneousLinks);
if (erroneousLinks.Count < 5) { context.LogError( ... ); }

곱셈 유효성 검사

최소 곱셈을 확인하기 위한 유효성 검사 방법은 DSL에 대해 자동으로 생성됩니다. 코드는 Dsl\Generated Code\MultiplicityValidation.cs 기록됩니다. 이러한 메서드는 DSL 탐색기의 Editor\Validation 노드에서 유효성 검사를 사용하도록 설정할 때 적용됩니다.

도메인 관계의 역할의 곱함을 1..* 또는 1..1로 설정했지만 사용자가 이 관계의 링크를 만들지 않으면 유효성 검사 오류 메시지가 나타납니다.

예를 들어 DSL에 사람(Person) 및 타운(Town) 클래스가 있고, Town 역할에 관계 1..\*이 있는 PersonLivesInTown 관계가 있는 경우, 타운이 없는 각 사람에 대해 오류 메시지가 표시됩니다.

프로그램 코드에서 유효성 검사 실행

ValidationController에 액세스하거나 만들어 유효성 검사를 실행할 수 있습니다. 오류 창에서 사용자에게 오류를 표시하려면 다이어그램의 DocData에 연결된 ValidationController를 사용합니다. 예를 들어 메뉴 명령을 작성하는 경우 명령 CurrentDocData.ValidationController 집합 클래스에서 사용할 수 있습니다.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Modeling.Shell;
...
partial class MyLanguageCommandSet
{
  private void OnMenuMyContextMenuCommand(object sender, EventArgs e)
  {
   ValidationController controller = this.CurrentDocData.ValidationController;
...

자세한 내용은 방법: 바로 가기 메뉴에 명령 추가를 참조하세요.

별도의 유효성 검사 컨트롤러를 만들고 오류를 직접 관리할 수도 있습니다. 다음은 그 예입니다.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Modeling.Shell;
...
Store store = ...;
VsValidationController validator = new VsValidationController(s);
// Validate all elements in the Store:
if (!validator.Validate(store, ValidationCategories.Save))
{
  // Deal with errors:
  foreach (ValidationMessage message in validator.ValidationMessages) { ... }
}

변경이 발생할 때 유효성 검사 실행

모델이 유효하지 않은 경우 사용자에게 즉시 경고를 표시하려면 유효성 검사를 실행하는 저장소 이벤트를 정의할 수 있습니다. 저장소 이벤트에 대한 자세한 내용은 이벤트 처리기가 모델 외부에서 변경 내용을 전파하는 것을 참조하세요.

유효성 검사 코드 외에도 다음 예제와 유사한 콘텐츠를 사용하여 DslPackage 프로젝트에 사용자 지정 코드 파일을 추가합니다. 이 코드는 문서에 첨부된 코드를 사용합니다 ValidationController . 이 컨트롤러는 Visual Studio 오류 목록에 유효성 검사 오류를 표시합니다.

using System;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
namespace Company.FamilyTree
{
  partial class FamilyTreeDocData // Change name to your DocData.
  {
    // Register the store event handler:
    protected override void OnDocumentLoaded()
    {
      base.OnDocumentLoaded();
      DomainClassInfo observedLinkInfo = this.Store.DomainDataDirectory
         .FindDomainClass(typeof(ParentsHaveChildren));
      DomainClassInfo observedClassInfo = this.Store.DomainDataDirectory
         .FindDomainClass(typeof(Person));
      EventManagerDirectory events = this.Store.EventManagerDirectory;
      events.ElementAdded
         .Add(observedLinkInfo, new EventHandler<ElementAddedEventArgs>(ParentLinkAddedHandler));
      events.ElementDeleted.Add(observedLinkInfo, new EventHandler<ElementDeletedEventArgs>(ParentLinkDeletedHandler));
      events.ElementPropertyChanged.Add(observedClassInfo, new EventHandler<ElementPropertyChangedEventArgs>(BirthDateChangedHandler));
    }
    // Handler will be called after transaction that creates a link:
    private void ParentLinkAddedHandler(object sender,
                                ElementAddedEventArgs e)
    {
      this.ValidationController.Validate(e.ModelElement,
           ValidationCategories.Save);
    }
    // Called when a link is deleted:
    private void ParentLinkDeletedHandler(object sender,
                                ElementDeletedEventArgs e)
    {
      // Don't apply validation to a deleted item!
      // - Validate store to refresh the error list.
      this.ValidationController.Validate(this.Store,
           ValidationCategories.Save);
    }
    // Called when any property of a Person element changes:
    private void BirthDateChangedHandler(object sender,
                      ElementPropertyChangedEventArgs e)
    {
      Person person = e.ModelElement as Person;
      // Not interested in changes in other properties:
      if (e.DomainProperty.Id != Person.BirthYearDomainPropertyId)
          return;

      // Validate all parent links to and from the person:
      this.ValidationController.Validate(
        ParentsHaveChildren.GetLinksToParents(person)
        .Concat(ParentsHaveChildren.GetLinksToChildren(person))
        , ValidationCategories.Save);
    }
  }
}

또한 처리기는 링크 또는 요소에 영향을 주는 실행 취소 또는 다시 실행 작업 후에 호출됩니다.

사용자 지정 유효성 검사 범주

메뉴 및 열기와 같은 표준 유효성 검사 범주 외에도 고유한 범주를 정의할 수 있습니다. 프로그램 코드에서 이러한 범주를 호출할 수 있습니다. 사용자가 직접 호출할 수 없습니다.

사용자 지정 범주의 일반적인 용도는 모델이 특정 도구의 전제 조건을 충족하는지 여부를 테스트하는 범주를 정의하는 것입니다.

특정 범주에 유효성 검사 메서드를 추가하려면 다음과 같은 특성으로 접두사를 지정합니다.

[ValidationMethod(CustomCategory = "PreconditionsForGeneratePartsList")]
[ValidationMethod(ValidationCategory.Menu)]
private void TestForCircularLinks(ValidationContext context)
{...}

비고

메서드에 원하는 만큼 많은 [ValidationMethod()] 특성을 추가할 수 있습니다. 사용자 지정 범주와 표준 범주 모두에 메서드를 추가할 수 있습니다.

사용자 지정 유효성 검사를 호출하려면 다음을 수행합니다.


// Invoke all validation methods in a custom category:
validationController.ValidateCustom
  (store, // or a list of model elements
   "PreconditionsForGeneratePartsList");

유효성 검사에 대한 대안

유효성 검사 제약 조건은 오류를 보고하지만 모델을 변경하지는 않습니다. 대신 모델이 유효하지 않게 하려면 다른 기술을 사용할 수 있습니다.

그러나 이러한 기술은 권장되지 않습니다. 일반적으로 사용자가 잘못된 모델을 수정하는 방법을 결정할 수 있도록 하는 것이 좋습니다.

모델을 유효성으로 복원하도록 변경 사항을 조정합니다. 예를 들어 사용자가 허용되는 최대값보다 높은 속성을 설정하는 경우 속성을 최대값으로 다시 설정할 수 있습니다. 이렇게 하려면 규칙을 정의합니다. 자세한 내용은 모델 내의 변경 내용을 전파하는 규칙을 참조하세요.

잘못된 변경이 시도되면 트랜잭션을 롤백합니다. 이 용도로 규칙을 정의할 수도 있지만 경우에 따라 속성 처리기 OnValueChanging()을 재정의하거나 메서드를 재정의할 수 있습니다. 트랜잭션을 롤백하려면 자세한 내용은 도메인 속성 값 변경 처리기를 참조하세요.

경고

사용자가 변경 내용이 조정되었거나 롤백되었음을 알고 있는지 확인합니다. 예를 들어 System.Windows.Forms.MessageBox.Show("message").를 사용합니다.