Поделиться через


Проверка в доменных языках

Автор доменного языка может определить ограничения проверки, позволяющие контролировать полноценность созданной пользователем модели. Например, если доменный язык позволяет рисовать генеалогическое дерево людей и их предков, можно написать ограничения, согласно которым даты рождения детей должны быть позже дат рождения родителей.

Ограничения проверки могут выполняться, когда модель сохраняется, когда она открыта, и когда пользователь явно запускает команду меню "Проверка ". Также можно выполнять проверку с помощью другого элемента управления программы. Например, можно выполнять проверку в ответ на изменение в значении или отношении свойства.

Проверка особенно важна, если вы пишете текстовые шаблоны или другие средства, обрабатывающие модели пользователей. Проверка гарантирует, что модели удовлетворяют предусловиям, указанным в этих инструментах.

Предупреждение

Также можно определить ограничения проверки в отдельных расширениях доменного языка вместе с командами меню расширения и обработчиками жестов. Пользователи могут выбрать установку таких расширений в дополнение к доменному языку. Дополнительные сведения см. в разделе "Расширение DSL" с помощью MEF.

Запуск проверки

Когда пользователь редактирует модель, т. е. экземпляр доменного языка, запустить проверку могут следующие действия.

  • Щелкните схему правой кнопкой мыши и выберите "Проверить все".

  • Щелкните правой кнопкой мыши верхний узел в Обозреватель 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 имеет роли, которые называются Child и Parent.

    DSL Definition diagram - family tree model

Категории проверки

В атрибуте ValidationMethodAttribute можно указать, когда метод проверки должен быть выполнен.

Категория Выполнение
ValidationCategories Когда пользователь вызывает команду меню "Проверка".
ValidationCategories Когда открывается файл модели.
ValidationCategories Когда сохраняется файл модели. При наличии ошибок проверки пользователю предлагается отменить или сохранить операцию.
ValidationCategories Когда сохраняется файл модели. При наличии ошибок из-за методов в этой категории пользователь предупреждается о том, что повторное открытие файла может быть невозможно.

Используйте данную категорию для методов проверки, которые проверяют дубликаты имен или идентификаторов или другие условия, способные вызвать ошибки загрузки.
ValidationCategories Когда вызывается метод 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\Generated Code\MultiplicityValidation.cs. Эти методы вступают в силу при включении проверки в узле editor\Validation в dsL Обозреватель.

Если кратность роли доменного отношения равна 1..* или 1..1, но пользователь не создает ссылку на это отношение, появляется сообщение об ошибке.

Например, если ваш DSL имеет классы Person и Town, а связь PersonLivesInTown с отношением 1..\* на роли города, то для каждого человека, у которого нет города, появится сообщение об ошибке.

Запуск проверки из кода программы

Можно запустить проверку, имея доступ к ValidationController или создав его. Если вы хотите, чтобы ошибки отображались пользователю в окне ошибки, используйте ValidationController, подключенный к docData схемы. Например, при написании команды меню 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")..