Validação em uma linguagem específica do domínioValidation in a Domain-Specific Language

Como o autor de uma linguagem específica de domínio (DSL), você pode definir restrições de validação para verificar se o modelo criado pelo usuário é significativo.As the author of a domain-specific language (DSL), you can define validation constraints to verify that the model created by the user is meaningful. Por exemplo, se a sua DSL permite que os usuários desenhem uma árvore genealógica das pessoas e os seus ancestrais, você pode escrever uma restrição que garanta que os filhos tenham datas de nascimento posteriores as dos seus pais.For example, if your DSL allows users to draw a family tree of people and their ancestors, you could write a constraint that ensures that children have birth dates after their parents.

Você pode ter restrições de validação executado quando o modelo é salvo, quando ele é aberto, e quando o usuário executa explicitamente o validar comando de menu.You can have the validation constraints execute when the model is saved, when it is opened, and when the user explicitly runs the Validate menu command. Você também pode executar a validação no controle do programa.You can also execute validation under program control. Por exemplo, você pode executar a validação em resposta à alteração de um valor de propriedade ou relação.For example, you could execute validation in response to a change in a property value or relationship.

A validação é particularmente importante se você estiver escrevendo modelos de texto ou outras ferramentas que processam modelos dos seus usuários.Validation is particularly important if you are writing text templates or other tools that process your users' models. A validação assegura que os modelos atendam as pré-condições presumidas por essas ferramentas.Validation ensures that the models fulfill the preconditions assumed by those tools.

Warning

Você também pode permitir que restrições de validação sejam definidas em extensões separadas para a sua DSL, com os comandos de menu e manipuladores de gestos de extensão.You can also allow validation constraints to be defined in separate extensions to your DSL, along with extension menu commands and gesture handlers. Os usuários podem optar por instalar essas extensões além da sua DSL.Users can choose to install these extensions in addition to your DSL. Para obter mais informações, consulte estender a DSL usando MEF.For more information, see Extend your DSL by using MEF.

Executando a validaçãoRunning Validation

Quando um usuário está editando um modelo, ou seja, uma instância da sua linguagem específica de domínio, as seguintes ações podem executar a validação:When a user is editing a model, that is, an instance of your domain-specific language, the following actions can run validation:

  • O diagrama com o botão direito e selecione validar todos.Right-click the diagram and select Validate All.

  • Clique com botão direito no nó superior no Gerenciador da sua DSL e selecione validar todosRight-click the top node in the Explorer of your DSL and select Validate All

  • Salve o modelo.Save the model.

  • Abra o modelo.Open the model.

  • Além disso, você pode escrever o código do programa que executada a validação, por exemplo, como parte de um comando de menu ou em resposta a uma alteração.In addition, you can write program code that runs validation, for example, as part of a menu command or in response to a change.

    Erros de validação aparecerá na Error List janela.Any validation errors will appear in the Error List window. O usuário pode clicar duas vezes em uma mensagem de erro para selecionar os elementos do modelo que são a causa do erro.The user can double-click an error message to select the model elements that are the cause of the error.

Definindo restrições de validaçãoDefining Validation Constraints

Você define restrições de validação adicionando métodos de validação às classes ou relações de domínio da sua DSL.You define validation constraints by adding validation methods to the domain classes or relationships of your DSL. Quando a validação é executada pelo usuário ou sob o controle do programa, alguns ou todos os métodos de validação são executados.When validation is run, either by the user or under program control, some or all of the validation methods are executed. Cada método é aplicado a cada instância de sua classe, não pode haver vários métodos de validação em cada classe.Each method is applied to each instance of its class, and there can be several validation methods in each class.

Cada método de validação relata os erros que encontra.Each validation method reports any errors that it finds.

Note

Os métodos de validação relatam erros, mas não alteram o modelo.Validation methods report errors, but do not change the model. Se você quiser ajustar ou evitar certas alterações, consulte alternativas de validação.If you want to adjust or prevent certain changes, see Alternatives to Validation.

Para definir uma restrição de validaçãoTo define a validation constraint

  1. Habilitar a validação na Editor \ validação nó:Enable validation in the Editor\Validation node:

    1. Abra Dsl\DslDefinition.dsl.Open Dsl\DslDefinition.dsl.

    2. No DSL Explorer, expanda o Editor nó e selecione validação.In DSL Explorer, expand the Editor node and select Validation.

    3. Na janela Propriedades, defina as usa propriedades a serem true.In the Properties window, set the Uses properties to true. Esse é o modo mais conveniente de definir todas essas propriedades.It is most convenient to set all these properties.

    4. Clique em transformar todos os modelos na Gerenciador de soluções barra de ferramentas.Click Transform All Templates in the Solution Explorer toolbar.

  2. Escreva definições de classe parciais para uma ou mais de suas classes de domínio ou relações de domínio.Write partial class definitions for one or more of your domain classes or domain relationships. Escreva essas definições em um novo arquivo de código na Dsl projeto.Write these definitions in a new code file in the Dsl project.

  3. Prefixe cada classe com este atributo:Prefix each class with this attribute:

    [ValidationState(ValidationState.Enabled)]
    
    • Por padrão, esse atributo também permite a validação de classes derivadas.By default, this attribute will also enable validation for derived classes. Se você deseja desabilitar a validação para uma classe derivada específica, use ValidationState.Disabled.If you want to disable validation for a specific derived class, you can use ValidationState.Disabled.
  4. Adicione métodos de validação às classes.Add validation methods to the classes. Cada método de validação pode ter qualquer nome, mas tem um parâmetro do tipo ValidationContext.Each validation method can have any name, but have one parameter of type ValidationContext.

    Ele deve ser prefixado com um ou mais atributos ValidationMethod:It must be prefixed with one or more ValidationMethod attributes:

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

    Os ValidationCategories especificam quando o método é executado.The ValidationCategories specify when the method is executed.

    Por exemplo:For example:

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);
    }
  }

Observe os seguintes pontos sobre esse código:Notice the following points about this code:

  • Você pode adicionar métodos de validação às classes de domínio ou relações de domínio.You can add validation methods to domain classes or domain relationships. O código para esses tipos está no Dsl\Generated Code\Domain*. CS.The code for these types is in Dsl\Generated Code\Domain*.cs.

  • Cada método de validação é aplicado a todas as instâncias de sua classe e suas subclasses.Each validation method is applied to every instance of its class and its subclasses. No caso de uma relação de domínio, cada instância é um link entre dois elementos de modelo.In the case of a domain relationship, each instance is a link between two model elements.

  • Métodos de validação não são aplicados em ordem específica e cada método não é aplicado às instâncias da sua classe em ordem previsível.Validation methods are not applied in any specified order, and each method is not applied to the instances of its class in any predictable order.

  • Geralmente, não é uma boa prática um método de validação atualizar o conteúdo do repositório, porque isso levaria a resultados inconsistentes.It is usually bad practice for a validation method to update the store content, because this would lead to inconsistent results. Em vez disso, o método deve relatar qualquer erro chamando context.LogError, LogWarning ou LogInfo.Instead, the method should report any error by calling context.LogError, LogWarning or LogInfo.

  • Na chamada LogError, você pode fornecer uma lista de elementos de modelo ou links de relações que serão selecionados quando o usuário clicar duas vezes na mensagem de erro.In the LogError call, you can provide a list of model elements or relationship links that will be selected when the user double-clicks the error message.

  • Para obter informações sobre como ler o modelo no código do programa, consulte Navegando e atualizando um modelo no código do programa.For information about how to read the model in program code, see Navigating and Updating a Model in Program Code.

    O exemplo aplica-se ao seguinte modelo de domínio.The example applies to the following domain model. A relação ParentsHaveChildren tem funções que são nomeadas Child e Parent.The ParentsHaveChildren relationship has roles that are named Child and Parent.

    Diagrama de definição de DSL - modelo de árvore genealógica

Categorias de validaçãoValidation Categories

No atributo ValidationMethodAttribute, você especifica quando o método de validação deve ser executado.In the ValidationMethodAttribute attribute, you specify when the validation method should be executed.

CategoriaCategory ExecuçãoExecution
ValidationCategories Quando o usuário chama o comando de menu Validar.When the user invokes the Validate menu command.
ValidationCategories Quando o arquivo de modelo é aberto.When the model file is opened.
ValidationCategories Quando o arquivo é salvo.When the file is saved. Se houver erros de validação, o usuário terá a opção de cancelar a operação de salvamento.If there are validation errors, the user will be given the option of canceling the save operation.
ValidationCategories Quando o arquivo é salvo.When the file is saved. Se houver erros de métodos nesta categoria, o usuário é avisado que pode não ser possível abrir o arquivo novamente.If there are errors from methods in this category, the user is warned that it might not be possible to re-open the file.

Use essa categoria para métodos de validação que testem nomes duplicados ou IDs, ou outras condições que possam causar erros de carregamento.Use this category for validation methods that test for duplicated names or IDs, or other conditions that might cause loading errors.
ValidationCategories Quando o método ValidateCustom é chamado.When the ValidateCustom method is called. Validações nessa categoria podem ser invocadas somente a partir do código do programa.Validations in this category can be invoked only from program code.

Para obter mais informações, consulte categorias de validação personalizadas.For more information, see Custom Validation Categories.

Onde colocar métodos de validaçãoWhere to Place Validation Methods

Normalmente, você pode conseguir o mesmo efeito colocando um método de validação em um tipo diferente.You can often achieve the same effect by placing a validation method on a different type. Por exemplo, você pode adicionar um método à classe Person, em vez da relação ParentsHaveChildren, e fazê-la iterar nos links:For example, you could add a method to the Person class instead of the ParentsHaveChildren relationship, and have it iterate through the links:

[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)
        { ...

Agregando restrições de validação.Aggregating validation constraints. Para aplicar validação em uma ordem previsível, defina um único método de validação em uma classe de proprietário, o elemento raiz do seu modelo.To apply validation in a predictable order, define a single validation method on an owner class, such the root element of your model. Essa técnica também permite agregar vários relatórios de erros em uma única mensagem.This technique also lets you aggregate multiple error reports into a single message.

As desvantagens são que o método combinado é menos fácil de gerenciar e que todas as restrições devem ter as mesmas ValidationCategories.Drawbacks are that the combined method is less easy to manage, and that the constraints must all have the same ValidationCategories. Por isso, recomendamos que você mantenha cada restrição em um método separado, se possível.We therefore recommend that you keep each constraint in a separate method if possible.

Passando valores no cache de contexto.Passing values in the context cache. O parâmetro de contexto tem um dicionário no qual você pode colocar valores arbitrários.The context parameter has a dictionary into which you can place arbitrary values. O dicionário persiste durante a duração da execução da validação.The dictionary persists for the life of the validation run. Um método de validação específico pode, por exemplo, manter uma contagem de erros no contexto e usá-la para evitar sobrecarregar a janela de erro com mensagens repetidas.A particular validation method could, for example, keep an error count in the context, and use it to avoid flooding the error window with repeated messages. Por exemplo:For example:

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( ... ); }

Validação de multiplicidadesValidation of Multiplicities

Métodos de validação para verificar a multiplicidade mínima são gerados automaticamente para a sua DSL.Validation methods for checking minimum multiplicity are automatically generated for your DSL. O código é gravado em Dsl\Generated Code\MultiplicityValidation.cs.The code is written to Dsl\Generated Code\MultiplicityValidation.cs. Esses métodos têm efeito quando você habilita a validação na Editor \ validação nó no Gerenciador de DSL.These methods take effect when you enable validation in the Editor\Validation node in DSL Explorer.

Se você definir que a multiplicidade de uma função de uma relação de domínio como 1..* ou 1..1, mas o usuário não criar um link dessa relação, uma mensagem de erro de validação aparecerá.If you set the multiplicity of a role of a domain relationship to be 1..* or 1..1, but the user does not create a link of this relationship, a validation error message will appear.

Por exemplo, se a sua DSL tem classes Person e Town e uma relação PersonLivesInTown com uma relação 1...\* na função Town, em seguida, para cada pessoa que não tem Town, uma mensagem de erro será exibida.For example, if your DSL has classes Person and Town, and a relationship PersonLivesInTown with a relationship 1..\* at the Town role, then for each Person that has no Town, an error message will appear.

Executando a validação a partir do código do programaRunning Validation from Program Code

Você pode executar a validação acessando ou criando um ValidationController.You can run validation by accessing or creating a ValidationController. Se você deseja que os erros a ser exibido para o usuário na janela de erro, use o ValidationController que está anexado ao DocData diagrama.If you want the errors to be displayed to the user in the error window, use the ValidationController that is attached to your diagram's DocData. Por exemplo, se você estiver escrevendo um comando de menu, CurrentDocData.ValidationController está disponível na classe de conjunto de comandos:For example, if you are writing a menu command, CurrentDocData.ValidationController is available in the command set class:

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;
...

Para obter mais informações, consulte como: adicionar um comando ao Menu de atalho.For more information, see How to: Add a Command to the Shortcut Menu.

Você também pode criar um controlador de validação independente e gerenciar os erros.You can also create a separate validation controller, and manage the errors yourself. Por exemplo:For example:

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) { ... }
}

Executando a validação quando ocorre uma alteraçãoRunning validation when a change occurs

Se você deseja garantir que o usuário seja avisado imediatamente quando o modelo se tornar inválido, defina um evento de armazenamento que execute a validação.If you want to make sure that the user is warned immediately if the model becomes invalid, you can define a store event that runs validation. Para obter mais informações sobre eventos de armazenamento, consulte manipuladores de propagar alterações fora o modelo de evento.For more information about store events, see Event Handlers Propagate Changes Outside the Model.

Além do código de validação, adicione um arquivo de código personalizado ao seu DslPackage projeto, com conteúdo semelhante ao exemplo a seguir.In addition to the validation code, add a custom code file to your DslPackage project, with content similar to the following example. Esse código usa o ValidationController que é anexado ao documento.This code uses the ValidationController that is attached to the document. Esse controlador mostra os erros de validação na lista de erros do Visual Studio.This controller displays the validation errors in the Visual Studio error list.

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);
    }
  }
}

Os manipuladores também são chamados depois de operações Undo ou Redo que afetam os links ou elementos.The handlers are also called after Undo or Redo operations that affect the links or elements.

Categorias de validação personalizadasCustom Validation Categories

Além das categorias de validação padrão, como Menu e Open, você pode definir suas próprias categorias.In addition to the standard validation categories, such as Menu and Open, you can define your own categories. Você pode invocar essas categorias do código do programa.You can invoke these categories from program code. O usuário não pode invocá-las diretamente.The user cannot invoke them directly.

Um uso típico de categorias personalizadas é definir uma categoria que teste se o modelo satisfaz as pré-condições de uma ferramenta específica.A typical use for custom categories is to define a category that tests whether the model satisfies the preconditions of a particular tool.

Para adicionar um método de validação à uma categoria específica, prefixe-o com um atributo como este:To add a validation method to a particular category, prefix it with an attribute like this:

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

Note

Você pode prefixar um método com a quantidade de atributos [ValidationMethod()] você desejar.You can prefix a method with as many [ValidationMethod()] attributes as you want. Você pode adicionar um método a categorias personalizadas e padrão.You can add a method to both custom and standard categories.

Para invocar a validação personalizada:To invoke a custom validation:


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

Alternativas de validaçãoAlternatives to Validation

As restrições de validação relatam erros, mas não alteram o modelo.Validation constraints report errors, but do not change the model. Se, ao contrário, você deseja evitar que o modelo se torne inválido, você pode usar outras técnicas.If, instead, you want to prevent the model becoming invalid, you can use other techniques.

No entanto, essas técnicas não são recomendadas.However, these techniques are not recommended. Normalmente, é melhor deixar que o usuário decida como corrigir um modelo inválido.It is usually better to let the user decide how to correct an invalid model.

Ajuste a alteração para restaurar o modelo para validade.Adjust the change to restore the model to validity. Por exemplo, se o usuário define uma propriedade acima do máximo permitido, você pode redefinir a propriedade ao valor máximo.For example, if the user sets a property above the allowed maximum, you could reset the property to the maximum value. Para fazer isso, defina uma regra.To do this, define a rule. Para obter mais informações, consulte propagam alterações dentro do modelo de regras.For more information, see Rules Propagate Changes Within the Model.

Reverta a transação se uma alteração inválida é tentada.Roll back the transaction if an invalid change is attempted. Você também pode definir uma regra para essa finalidade, mas em alguns casos, é possível substituir um manipulador de propriedade Onvaluechanging, ou substituir um método, como OnDeleted(). para reverter uma transação, use this.Store.TransactionManager.CurrentTransaction.Rollback(). para obter mais informações obter informações, consulte manipuladores de alteração de valor de propriedade de domínio.You could also define a rule for this purpose, but in some cases it is possible to override a property handler OnValueChanging(), or to override a method such as OnDeleted(). To roll back a transaction, use this.Store.TransactionManager.CurrentTransaction.Rollback(). For more information, see Domain Property Value Change Handlers.

Warning

Verifique se o usuário sabe que a alteração foi ajustada ou revertida.Make sure that the user knows that the change has been adjusted or rolled back. Por exemplo, use System.Windows.Forms.MessageBox.Show("message").For example, use System.Windows.Forms.MessageBox.Show("message").

Consulte tambémSee Also