域特定语言中的验证

为域特定语言 (dsl) 的作者 (DSL),您可以定义验证约束验证用户创建的模型是有意义的。 例如, DSL,如果允许用户绘制方及其上级系族树,您可以编写确保的约束子项其父后的出生日期。

可以使验证约束执行该模型时,保存,打开时,它,因此,当用户显式运行 验证 菜单命令时。 您也可以在验证程序控件下。 例如,您可以执行验证响应属性值或关系中的更改。

验证尤其重要,如果要编写处理用户的模型的文本模板或其他工具。 验证确保模型执行这些工具假定的前置条件。

警告

可以在单独的扩展还允许验证约束定义到 DSL,扩展菜单命令和笔势处理程序。除了 DSL 外,用户可以选择安装这些扩展。有关更多信息,请参见 使用 MEF 扩展 DSL

运行验证

当用户编辑模型,也就是说,将域特定语言 (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 方法调用。 此类别的验证可以从程序代码才调用。

有关更多信息,请参见 自定义验证类别。

在何处放置验证方法

可以通过将验证方法通常获得相同的效果在不同的类型。 例如,可以向 person 类而不是 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编写。 ,当您在 编辑 \Validation 节点的验证在 DSL 资源管理器时,这些方法生效。

如果将域关系的角色的重数为 1. * 或 1..1,但是,用户不创建此关系链接,验证错误信息将显示。

例如,因此,如果 DSL 具有类人员和 Town 和一个关系 1.* 的关系 PersonLivesInTown 在 Town 角色,则没有 Town 的每个人员的错误消息,将出现。

运行从程序代码的验证

您可以验证由访问或创建 ValidationController 运行。 如果希望错误会显示为 false 窗口的用户,请使用附加到关系图的 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(). 有关更多信息,请参见 域属性值更改处理程序

警告

确保该用户知道调整了更改或回滚。例如,使用 System.Windows.Forms.MessageBox.Show("message").

请参见

概念

在程序代码中导航和更新模型

事件处理程序在模型外部传播更改