Convalida in un linguaggio specifico di dominio

Gli autori di un linguaggio specifico di dominio possono definire vincoli di convalida per verificare che il modello creato dall'utente sia significativo. Ad esempio, se il linguaggio specifico di dominio consente agli utenti di disegnare l'albero genealogico di determinate persone e dei relativi antenati, è possibile scrivere un vincolo per garantire che le date di nascita dei figli siano successive a quelle dei genitori.

È possibile che i vincoli di convalida vengano eseguiti quando il modello viene salvato, quando viene aperto e quando l'utente esegue in modo esplicito il comando di menu Convalida . È anche possibile eseguire la convalida sotto il controllo del programma, ad esempio in risposta alla modifica di un valore di proprietà o di una relazione.

La convalida è particolarmente importante se si scrivono modelli di testo o altri strumenti che elaborano i modelli degli utenti. Garantisce infatti che i modelli siano conformi alle precondizioni previste da questi strumenti.

Avviso

È anche possibile consentire la definizione di vincoli di convalida in estensioni separate del linguaggio specifico di dominio, unitamente a comandi di menu e gestori movimenti dell'estensione. Gli utenti potranno quindi scegliere di installare queste estensioni in aggiunta al linguaggio specifico di dominio. Per altre informazioni, vedere Estendere il linguaggio DSL tramite MEF.

Esecuzione della convalida

Quando un utente modifica un modello, ovvero un'istanza del linguaggio specifico di dominio, la convalida viene eseguita in seguito alle azioni seguenti:

  • Fare clic con il pulsante destro del mouse sul diagramma e scegliere Convalida tutto.

  • Fare clic con il pulsante destro del mouse sul nodo superiore in Explorer del linguaggio DSL e scegliere Convalida tutto

  • L'utente salva il modello.

  • L'utente apre il modello.

  • È anche possibile scrivere codice programma che esegue la convalida, ad esempio includendolo in un comando di menu oppure in risposta a una modifica.

    Eventuali errori di convalida verranno visualizzati nella finestra Elenco errori . L'utente può fare doppio clic su un messaggio di errore per selezionare gli elementi del modello che sono la causa dell'errore.

Definizione dei vincoli di convalida

Per definire vincoli di convalida, è possibile aggiungere metodi di convalida alle classi di dominio o alle relazioni del linguaggio specifico di dominio. Durante l'esecuzione della convalida, avviata dall'utente o controllata dal programma, vengono eseguiti alcuni o tutti i metodi di convalida. Ogni metodo viene applicato a ogni istanza della propria classe e possono essere presenti diversi metodi di convalida in ogni classe.

Ogni metodo di convalida segnala gli eventuali errori trovati.

Nota

I metodi di convalida segnalano gli errori, ma non modificano il modello. Per modificare o impedire determinate modifiche, vedere Alternative alla convalida.

Per definire un vincolo di convalida

  1. Abilitare la convalida nel nodo Editor\Validation :

    1. Aprire Dsl\DslDefinition.dsl.

    2. In Esplora DSL espandere il nodo Editor e selezionare Convalida.

    3. Nella Finestra Proprietà impostare le proprietà Uses su true. È più pratico impostare tutte queste proprietà.

    4. Fare clic su Trasforma tutti i modelli nella barra degli strumenti Esplora soluzioni.

  2. Scrivere le definizioni di classe parziali per una o più classi di dominio o relazioni di dominio Scrivere queste definizioni in un nuovo file di codice nel progetto Dsl .

  3. Aggiungere questo attributo all'inizio di ogni classe:

    [ValidationState(ValidationState.Enabled)]
    
    • Per impostazione predefinita, questo attributo abiliterà inoltre la convalida per le classi derivate. Per disabilitare la convalida per una classe derivata specifica, è possibile usare ValidationState.Disabled.
  4. Aggiungere i metodi di convalida alle classi. Al metodo di convalida è possibile assegnare un nome qualsiasi, ma deve includere un parametro di tipo ValidationContext.

    Deve inoltre essere preceduto da uno o più attributi ValidationMethod:

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

    Gli attributi ValidationCategories indicano quando viene eseguito il metodo.

    Ad esempio:

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

Notare gli aspetti seguenti su questo codice:

  • È possibile aggiungere metodi di convalida a classi di dominio o relazioni di dominio. Il codice per questi tipi è in Dsl\Generated Code\Domain*.cs.

  • Ogni metodo di convalida viene applicato a ogni istanza delle relative classi e sottoclassi. Nel caso di una relazione di dominio ogni istanza è un collegamento tra due elementi del modello.

  • I metodi di convalida non vengono applicati in qualsiasi ordine specificato e ogni metodo non viene applicato alle istanze della propria classe in qualsiasi ordine prevedibile.

  • È in genere preferibile evitare che un metodo di convalida aggiorni il contenuto dell'archivio perché potrebbe causare risultati incoerenti. Il metodo deve invece segnalare eventuali errori chiamando context.LogError, LogWarning o LogInfo.

  • Nella chiamata a LogError è possibile fornire un elenco di elementi di modello o collegamenti di relazione che verranno selezionati quando l'utente fa doppio clic sul messaggio di errore.

  • Per informazioni su come leggere il modello nel codice del programma, vedere Esplorazione e aggiornamento di un modello nel codice programma.

    L'esempio si applica al seguente modello di dominio. La relazione ParentsHaveChildren include ruoli denominati Child e Parent.

    DSL Definition diagram - family tree model

Categorie di convalida

Nell'attributo ValidationMethodAttribute viene specificato quando eseguire il metodo di convalida.

Category Esecuzione
ValidationCategories Quando l'utente richiama il comando di menu Convalida.
ValidationCategories Quando viene aperto il file di modello.
ValidationCategories Quando viene salvato il file di modello. Se sono presenti errori di convalida, l'utente potrà scegliere se annullare l'operazione di salvataggio.
ValidationCategories Quando viene salvato il file di modello. Se sono presenti errori di metodi in questa categoria, l'utente viene avvertito che potrebbe risultare impossibile riaprire il file.

Usare questa categoria per i metodi di convalida che verificano l'esistenza di nomi o ID duplicati o altre condizioni che potrebbero causare errori di caricamento.
ValidationCategories Quando viene chiamato il metodo ValidateCustom. Le convalide in questa categoria possono essere richiamate solo dal codice programma.

Per altre informazioni, vedere Categorie di convalida personalizzate.

Dove inserire i metodi di convalida

È spesso possibile ottenere lo stesso effetto inserendo un metodo di convalida in un tipo diverso. Ad esempio, è possibile aggiungere un metodo alla classe Person invece della relazione ParentsHaveChildren e fare in modo che esegua l'iterazione tramite i collegamenti:

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

Aggregazione dei vincoli di convalida. Per applicare la convalida in un ordine prevedibile, definire un singolo metodo di convalida in una classe proprietario, ad esempio l'elemento radice del modello. Questa tecnica consente inoltre di aggregare più segnalazioni in un unico messaggio.

Presenta però alcuni svantaggi, in quanto il metodo combinato è più difficile da gestire e i vincoli devono includere tutti gli stessi attributi ValidationCategories. Se possibile, è quindi consigliabile mantenere ogni vincolo in un metodo separato.

Passaggio di valori nella cache del contesto. Il parametro context ha un dizionario in cui è possibile inserire valori arbitrari. Il dizionario viene mantenuto per tutta la durata della convalida. Un particolare metodo di convalida potrebbe, ad esempio, mantenere un conteggio degli errori nel contesto e usarlo per evitare di visualizzare nella finestra degli errori un numero elevato di messaggi ripetuti. Ad esempio:

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

Convalida delle molteplicità

I metodi di convalida per verificare la molteplicità minima vengono generati automaticamente per il linguaggio specifico di dominio. Il codice viene scritto in Dsl\Generated Code\MultiplicityValidation.cs. Questi metodi diventano effettivi quando si abilita la convalida nel nodo Editor\Validation in Esplora DSL.

Se si imposta la molteplicità di un ruolo di una relazione di dominio su 1..* o 1..1, ma l'utente non crea un collegamento di questa relazione, verrà visualizzato un messaggio di errore di convalida.

Ad esempio, se il linguaggio DSL ha classi Person e Town e una relazione PersonLivesInTown con una relazione 1..\* nel ruolo Città, verrà visualizzato un messaggio di errore per ogni persona che non ha città.

Esecuzione della convalida dal codice programma

È possibile eseguire la convalida creando o accedendo a un oggetto ValidationController. Se si desidera che gli errori vengano visualizzati all'utente nella finestra di errore, usare ValidationController associato a DocData del diagramma. Ad esempio, se si intende scrivere un comando di menu, CurrentDocData.ValidationController è disponibile nella classe del set di comandi:

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

Per altre informazioni, vedere Procedura: Aggiungere un comando al menu di scelta rapida.

È anche possibile creare un controller di convalida separato e gestire gli errori manualmente. Ad esempio:

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

Esecuzione della convalida in caso di modifica

Se si vuole che l'utente venga avvertito immediatamente nel caso in cui il modello non è più valido, è possibile definire un evento dell'archivio che esegue la convalida. Per altre informazioni sugli eventi di archiviazione, vedere I gestori eventi propagano le modifiche all'esterno del modello.

Oltre al codice di convalida, aggiungere un file di codice personalizzato al progetto DslPackage , con contenuto simile all'esempio seguente. Questo codice usa il controller ValidationController allegato al documento. Questo controller visualizza gli errori di convalida nell'elenco degli errori di 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);
    }
  }
}

I gestori vengono inoltre chiamati dopo operazioni Undo o Redo che influiscono su collegamenti o elementi.

Categorie di convalida personalizzate

Oltre alle categorie di convalida standard, come Menu e Open, è possibile definire categorie personalizzate e richiamarle dal codice programma. L'utente non può invece richiamarle direttamente.

Le categorie personalizzate vengono in genere usate per definire una categoria che verifica se il modello soddisfa le precondizioni di un determinato strumento.

Per aggiungere un metodo di convalida a una determinata categoria, specificare prima del nome un attributo simile al seguente:

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

Nota

Prima di un metodo è possibile specificare il numero desiderato di attributi [ValidationMethod()]. È possibile aggiungere un metodo sia a categorie standard che personalizzate.

Per richiamare una convalida personalizzata:


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

Alternative alla convalida

I vincoli di convalida segnalano gli errori, ma non modificano il modello. Se, invece, si vuole evitare che il modello non sia più valido, è possibile usare altre tecniche,

che però non sono consigliate. È in genere preferibile lasciare che sia l'utente a decidere come correggere un modello non valido.

Modificare la modifica per ripristinare la validità del modello. Ad esempio, se l'utente imposta una proprietà al di sopra del massimo consentito, è possibile reimpostare la proprietà sul valore massimo. definendo a tale scopo una regola. Per altre informazioni, vedere Regole propagate modifiche all'interno del modello.

Eseguire il rollback della transazione se viene tentata una modifica non valida. È anche possibile definire una regola a questo scopo, ma in alcuni casi è possibile eseguire l'override di un gestore di proprietà OnValueChanging() o di eseguire l'override di un metodo, OnDeleted(). ad esempio Per eseguire il rollback di una transazione, usare this.Store.TransactionManager.CurrentTransaction.Rollback(). Per altre informazioni, vedere Gestori di modifiche del valore della proprietà di dominio.

Avviso

Assicurarsi di informare l'utente dell'adattamento della modifica o del rollback. Ad esempio, usare System.Windows.Forms.MessageBox.Show("message").