次の方法で共有


Domain-Specific 言語での検証

ドメイン固有言語 (DSL) の作成者は、検証制約を定義して、ユーザーによって作成されたモデルが意味があることを確認できます。 たとえば、DSL でユーザーが人とその先祖の家系図を描画できる場合は、子供が親の後に生年月日を持つことが保証される制約を記述できます。

検証制約は、モデルが保存されたとき、モデルが開かれるとき、およびユーザーが明示的に [検証 ] メニュー コマンドを実行するときに実行できます。 また、プログラム制御下で検証を実行することもできます。 たとえば、プロパティ値またはリレーションシップの変更に応じて検証を実行できます。

検証は、ユーザーのモデルを処理するテキスト テンプレートやその他のツールを記述する場合に特に重要です。 検証により、モデルがこれらのツールで想定される前提条件を満たすことが保証されます。

Warnung

DSL に対する個別の拡張機能で、拡張機能メニュー コマンドやジェスチャ ハンドラーと共に検証制約を定義することもできます。 ユーザーは、DSL に加えて、これらの拡張機能をインストールすることを選択できます。 詳細については、「 MEF を使用して DSL を拡張する」を参照してください。

検証の実行

ユーザーがモデル (ドメイン固有言語のインスタンス) を編集している場合、次のアクションで検証を実行できます。

  • ダイアグラムを右クリックし、[ すべて検証] を選択します。

  • DSL のエクスプローラーで上部のノードを右クリックし、[すべて検証] を選択します

  • モデルを保存します。

  • モデルを開きます。

  • さらに、たとえばメニュー コマンドの一部として、または変更に応じて、検証を実行するプログラム コードを記述できます。

    検証エラーは、[ エラー一覧 ] ウィンドウに表示されます。 ユーザーはエラー メッセージをダブルクリックして、エラーの原因であるモデル要素を選択できます。

検証制約の定義

検証制約を定義するには、DSL のドメイン クラスまたはリレーションシップに検証メソッドを追加します。 ユーザーまたはプログラム制御下で検証を実行すると、検証メソッドの一部またはすべてが実行されます。 各メソッドはそのクラスの各インスタンスに適用され、各クラスには複数の検証メソッドを使用できます。

各検証メソッドは、検出されたエラーを報告します。

検証メソッドはエラーを報告しますが、モデルは変更しません。 特定の変更を調整または禁止する場合は、「 検証の代替手段」を参照してください。

検証制約を定義するには

  1. [エディター]\[検証] ノードで検証を有効にします。

    1. Dsl\DslDefinition.dsl を開きます。

    2. DSL エクスプローラーで、[ エディター ] ノードを展開し、[ 検証] を選択します。

    3. [プロパティ] ウィンドウで、[ Uses プロパティ] を trueに設定します。 これらのプロパティをすべて設定するのが最も便利です。

    4. ソリューション エクスプローラーのツール バーの [すべてのテンプレートの変換] をクリックします。

  2. 1 つ以上のドメイン クラスまたはドメイン リレーションシップの部分クラス定義を記述します。 Dsl プロジェクトの新しいコード ファイルにこれらの定義を記述します。

  3. 各クラスに次の属性のプレフィックスを付けます。

    [ValidationState(ValidationState.Enabled)]
    
    • 既定では、この属性では派生クラスの検証も有効になります。 特定の派生クラスの検証を無効にする場合は、 ValidationState.Disabledを使用できます。
  4. クラスに検証メソッドを追加します。 各検証メソッドは任意の名前を持つことができますが、 ValidationContext型のパラメーターを 1 つ持つことができます。

    プレフィックスには、1 つ以上の 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 にあります。

  • 各検証メソッドは、そのクラスとそのサブクラスのすべてのインスタンスに適用されます。 ドメイン リレーションシップの場合、各インスタンスは 2 つのモデル要素間のリンクです。

  • 検証メソッドは指定された順序では適用されず、各メソッドは予測可能な順序でそのクラスのインスタンスに適用されません。

  • 通常、検証メソッドがストアのコンテンツを更新するのは不適切な方法です。これは、結果に一貫性がないためです。 代わりに、メソッドは、 context.LogErrorLogWarning 、または LogInfoを呼び出してエラーを報告する必要があります。

  • LogError 呼び出しでは、ユーザーがエラー メッセージをダブルクリックしたときに選択されるモデル要素またはリレーションシップ リンクの一覧を指定できます。

  • プログラム コードでモデルを読み取る方法については、「プログラム コードでの モデルの移動と更新」を参照してください。

    この例は、次のドメイン モデルに適用されます。 ParentsHaveChildren リレーションシップには、Child と Parent という名前のロールがあります。

    DSL 定義図 - ファミリー ツリー モデル

検証カテゴリ

ValidationMethodAttribute属性では、検証メソッドを実行するタイミングを指定します。

カテゴリ Execution
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)
        { ...

検証制約の集計。 予測可能な順序で検証を適用するには、モデルのルート要素など、所有者クラスに対して 1 つの検証メソッドを定義します。 この手法では、複数のエラー レポートを 1 つのメッセージに集約することもできます。

欠点は、結合されたメソッドの管理が容易でなく、制約がすべて同じ 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 があり、リレーションシップ PersonLivesInTown が Town 役割での関係 1..\* を持つ場合、Town を持たない Person ごとにエラーメッセージが表示されます。

プログラム コードからの検証の実行

ValidationController にアクセスするか、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() をオーバーライドしたり、 OnDeleted(). トランザクションをロールバックするには、 this.Store.TransactionManager.CurrentTransaction.Rollback(). を使用するなどのメソッドをオーバーライドすることができます。詳細については、「 ドメイン プロパティ値の変更ハンドラー」を参照してください。

Warnung

変更が調整またはロールバックされたことをユーザーが認識していることを確認します。 たとえば、System.Windows.Forms.MessageBox.Show("message"). を使用します