Compartir vía


Validación en un lenguaje específico de dominio

Como autor de un lenguaje específico del dominio (DSL), puede definir restricciones de validación para comprobar que el modelo creado por el usuario es significativo. Por ejemplo, si su DSL permite a los usuarios dibujar un árbol familiar de personas y sus antepasados, podría escribir una restricción que garantice que los niños tengan fechas de nacimiento después de sus padres.

Puede hacer que las restricciones de validación se ejecuten cuando se guarda el modelo, cuando se abre y cuando el usuario ejecuta explícitamente el comando de menú Validar . También puede ejecutar la validación bajo el control del programa. Por ejemplo, podría ejecutar la validación en respuesta a un cambio en un valor de propiedad o relación.

La validación es especialmente importante si está escribiendo plantillas de texto u otras herramientas que procesan los modelos de los usuarios. La validación garantiza que los modelos cumplan las condiciones previas que asumen esas herramientas.

Advertencia

También puede permitir que las restricciones de validación se definan en extensiones separadas para su DSL, junto con los comandos del menú de extensión y los controladores de gestos. Los usuarios pueden optar por instalar estas extensiones además de su DSL. Para obtener más información, consulte Extensión del DSL mediante MEF.

Ejecución de la validación

Cuando un usuario está editando un modelo, es decir, una instancia del lenguaje específico del dominio, las siguientes acciones pueden ejecutar la validación:

  • Haga clic con el botón derecho en el diagrama y seleccione Validar todo.

  • Haga clic con el botón derecho en el nodo superior del Explorador del DSL y seleccione Validar todo.

  • Guarde el modelo.

  • Abra el modelo.

  • Además, puede escribir código de programa que ejecute la validación, por ejemplo, como parte de un comando de menú o en respuesta a un cambio.

    Los errores de validación aparecerán en la ventana Lista de errores. El usuario puede hacer doble clic en un mensaje de error para seleccionar los elementos del modelo que son la causa del error.

Definición de restricciones de validación

Para definir restricciones de validación, agregue métodos de validación a las clases de dominio o las relaciones del DSL. Cuando se ejecuta la validación, ya sea por el usuario o bajo el control de programa, se ejecutan algunos o todos los métodos de validación. Cada método se aplica a cada instancia de su clase y puede haber varios métodos de validación en cada clase.

Cada método de validación notifica los errores que encuentre.

Nota:

Los métodos de validación notifican errores, pero no cambian el modelo. Si desea ajustar o evitar determinados cambios, consulte Alternativas a la validación.

Para definir una restricción de validación

  1. Habilite la validación en el nodo Editor\Validation :

    1. Abra Dsl\DslDefinition.dsl.

    2. En el Explorador dsl, expanda el nodo Editor y seleccione Validación.

    3. En la ventana Propiedades, establezca las propiedades Uses en true. Es más conveniente establecer todas estas propiedades.

    4. Haga clic en Transformar todas las plantillas en la barra de herramientas del Explorador de soluciones.

  2. Escriba definiciones de clase parciales para una o varias de las clases de dominio o las relaciones de dominio. Escriba estas definiciones en un nuevo archivo de código en el proyecto dsl .

  3. Prefijo de cada clase con este atributo:

    [ValidationState(ValidationState.Enabled)]
    
    • De forma predeterminada, este atributo también habilitará la validación para las clases derivadas. Si desea deshabilitar la validación de una clase derivada específica, puede usar ValidationState.Disabled.
  4. Agregue métodos de validación a las clases. Cada método de validación puede tener cualquier nombre, pero tiene un parámetro de tipo ValidationContext.

    Debe estar precedido por uno o más atributos ValidationMethod.

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

    ValidationCategories especifica cuándo se ejecuta el método.

    Por ejemplo:

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 los siguientes puntos sobre este código:

  • Puede agregar métodos de validación a clases de dominio o relaciones de dominio. El código de estos tipos está en Dsl\Generated Code\Domain*.cs.

  • Cada método de validación se aplica a cada instancia de su clase y sus subclases. En el caso de una relación de dominio, cada instancia es un vínculo entre dos elementos del modelo.

  • Los métodos de validación no se aplican en ningún orden especificado y cada método no se aplica a las instancias de su clase en ningún orden predecible.

  • Normalmente, suele ser una mala práctica que un método de validación actualice el contenido del almacén, ya que esto provocaría resultados incoherentes. En su lugar, el método debe notificar cualquier error llamando a context.LogErroro LogWarningLogInfo.

  • En la llamada LogError, puede proporcionar una lista de elementos de modelo o vínculos de relación que se seleccionarán cuando el usuario haga doble clic en el mensaje de error.

  • Para obtener información sobre cómo leer el modelo en el código del programa, vea Navegar y actualizar un modelo en código de programa.

    El ejemplo se aplica al siguiente modelo de dominio. La relación ParentsHaveChildren tiene roles denominados Hijo y Padre.

    Diagrama de definición de DSL: modelo de árbol de familia

Categorías de validación

En el ValidationMethodAttribute atributo , se especifica cuándo se debe ejecutar el método de validación.

Categoría Execution
ValidationCategories Cuando el usuario invoca el comando de menú Validar.
ValidationCategories Cuando se abre el archivo de modelo.
ValidationCategories Cuando se guarda el archivo. Si hay errores de validación, al usuario se le dará la opción de cancelar la operación de guardado.
ValidationCategories Cuando se guarda el archivo. Si hay errores de métodos en esta categoría, se advierte al usuario de que es posible que no sea posible volver a abrir el archivo.

Use esta categoría para los métodos de validación que prueban nombres o identificadores duplicados u otras condiciones que podrían provocar errores de carga.
ValidationCategories Cuando se llama al método ValidateCustom. Las validaciones de esta categoría solo se pueden invocar desde código de programa.

Para obtener más información, consulte Categorías de validación personalizadas.

Dónde colocar métodos de validación

A menudo puede lograr el mismo efecto colocando un método de validación en un tipo diferente. Por ejemplo, podría agregar un método a la clase Person en lugar de la relación ParentHaveChildren y hacer iteración a través de los vínculos:

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

Agrupando restricciones de validación. Para aplicar la validación en un orden predecible, defina un único método de validación en una clase de propietario, como el elemento raíz del modelo. Esta técnica también permite agregar varios informes de errores en un solo mensaje.

Los inconvenientes son que el método combinado es menos fácil de administrar y que las restricciones deben tener todas las mismas ValidationCategories. Por lo tanto, se recomienda mantener cada restricción en un método independiente si es posible.

Pasar valores en la memoria caché de contexto. El parámetro de contexto tiene un diccionario en el que puede colocar valores arbitrarios. El diccionario persiste durante todo el período de validación. Un método de validación determinado podría, por ejemplo, mantener un recuento de errores en el contexto y usarlo para evitar saturar la ventana de error con mensajes repetidos. Por ejemplo:

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

Validación de multiplicidades

Los métodos de validación para comprobar la multiplicidad mínima se generan automáticamente para su DSL. El código se escribe en Dsl\Generated Code\MultiplicityValidation.cs. Estos métodos surten efecto al habilitar la validación en el nodo Editor\Validation en el Explorador dsl.

Si establece la multiplicidad de un rol de una relación de dominio como 1..* o 1..1, pero el usuario no crea un vínculo de esta relación, aparecerá un mensaje de error de validación.

Por ejemplo, si el DSL tiene clases Person y Town, y una relación PersonLivesInTown con una relación 1..\* en el rol Town, aparecerá un mensaje de error para cada persona que no tenga Town.

Ejecución de la validación desde el código del programa

Puede ejecutar la validación accediendo o creando un ValidationController. Si desea que los errores se muestren al usuario en la ventana de error, use el ValidationController que está adjunto a el DocData del diagrama. Por ejemplo, si está escribiendo un comando de menú, CurrentDocData.ValidationController está disponible en la clase de conjunto de comandos:

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 obtener más información, vea Cómo: Agregar un comando al menú contextual.

También puede crear un controlador de validación independiente y administrar los errores usted mismo. Por ejemplo:

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

Ejecución de la validación cuando se produce un cambio

Si desea asegurarse de que el usuario se advierte inmediatamente si el modelo deja de ser válido, puede definir un evento de almacén que ejecute la validación. Para obtener más información sobre los eventos de tienda, vea los Controladores de eventos: Propagar cambios fuera del modelo.

Además del código de validación, agregue un archivo de código personalizado al proyecto DslPackage , con contenido similar al ejemplo siguiente. Este código utiliza el ValidationController que está adjunto al documento. Este controlador muestra los errores de validación en la lista de errores de 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);
    }
  }
}

También se llama a los controladores después de las operaciones Deshacer o Rehacer que afectan a los vínculos o elementos.

Categorías de validación personalizadas

Además de las categorías de validación estándar, como Menú y Abrir, puede definir sus propias categorías. Puede invocar estas categorías desde el código del programa. El usuario no puede invocarlos directamente.

Un uso típico de las categorías personalizadas es definir una categoría que comprueba si el modelo cumple las condiciones previas de una herramienta determinada.

Para agregar un método de validación a una categoría determinada, prefijo con un atributo similar al siguiente:

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

Nota:

Puede prefijar un método con tantos [ValidationMethod()] atributos como desee. Puede agregar un método a categorías personalizadas y estándar.

Para invocar una validación personalizada:


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

Alternativas a la validación

Las restricciones de validación notifican errores, pero no cambian el modelo. Si, en su lugar, quiere evitar que el modelo no sea válido, puede usar otras técnicas.

Sin embargo, no se recomiendan estas técnicas. Normalmente es mejor permitir que el usuario decida cómo corregir un modelo no válido.

Ajuste el cambio para que el modelo vuelva a ser válido. Por ejemplo, si el usuario establece una propiedad por encima del máximo permitido, podría restablecer la propiedad al valor máximo. Para ello, defina una regla. Para obtener más información, consulte Reglas de propagación de cambios dentro del modelo.

Revierte la transacción si se intenta realizar un cambio no válido. También puede definir una regla para este propósito, pero en algunos casos es posible invalidar un controlador de propiedades OnValueChanging() o invalidar un método como OnDeleted(). Para revertir una transacción, use this.Store.TransactionManager.CurrentTransaction.Rollback(). Para obtener más información, vea Controladores de cambio de valor de propiedad de dominio.

Advertencia

Asegúrese de que el usuario sabe que el cambio se ha ajustado o revertido. Por ejemplo, use System.Windows.Forms.MessageBox.Show("message")..