共用方式為


網域指定的語言中的驗證

身為定義域專屬語言 (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 呼叫時,您可以提供模型項目或在使用者按兩下錯誤訊息時,就會選取的關聯性連結的清單。

  • 閱讀在 [程式碼模型的相關資訊,請參閱巡覽及更新程式碼中的模型

此範例適用於下列領域模型。ParentsHaveChildren 關聯性會有子系和父代命名的角色。

DSL 定義圖表 - 家譜模型

驗證類別

在ValidationMethod屬性,您指定何時應該執行的驗證方法。

分類

執行

Menu

當使用者叫用 [驗證] 功能表指令。

Open

開啟模型檔案時。

Save

儲存檔案時。是否有驗證錯誤,使用者指定選擇取消儲存作業。

Load

儲存檔案時。如果發生錯誤,這個類別的方法,它可能無法重新開啟該檔案警告使用者。

請使用此類別的測試為重複的名稱或 Id 的驗證方法或其他條件可能造成載入錯誤的。

Custom

當呼叫 ValidateCustom 方法。可以叫用這個分類中的驗證,只能從程式碼。

如需詳細資訊,請參閱自訂驗證類別。

如何將驗證方法

您通常可以得到相同的效果,藉由放置在不同的型別上的驗證方法。比方說,您可以將方法加入的使用者類別,而非 ParentsHaveChildren 的關聯性,並讓它逐一查看連結:

[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。這些方法才會生效,當您啟用驗證在 Editor\Validation DSL 總管] 中的節點。

如果您將設定為 1 的網域關聯性的角色的重數..* 或 1..1,但是使用者並不會建立此關係中,連結會顯示驗證錯誤訊息。

比方說,如果您的 DSL 類別人和城鎮,並具有關聯性的關聯性 PersonLivesInTown 1..* 在城鎮角色,然後為每個人都沒有城鎮,錯誤訊息將會顯示。

從程式碼執行驗證

您可以存取或建立 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; 
...

如需詳細資訊,請參閱 HOW TO:在捷徑功能表中加入命令

您也可以建立個別的驗證控制站,並管理您自己的錯誤。例如:

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().如需詳細資訊,請參閱網域屬性值變更處理常式

注意事項警告

請確定使用者知道的變更已調整或復原。例如,使用System.Windows.Forms.MessageBox.Show("message").

請參閱

概念

巡覽及更新程式碼中的模型

事件處理常式傳播模型外的變更