Validering på ett Domain-Specific språk

Som författare till ett domänspecifikt språk (DSL) kan du definiera verifieringsbegränsningar för att kontrollera att modellen som skapats av användaren är meningsfull. Om din DSL till exempel tillåter användare att rita ett släktträd med personer och deras förfäder kan du skriva en begränsning som säkerställer att barn har födelsedatum efter sina föräldrar.

Du kan köra verifieringsbegränsningarna när modellen sparas, när den öppnas och när användaren uttryckligen kör kommandot Verifiera meny. Du kan också köra validering under programkontroll. Du kan till exempel köra validering som svar på en ändring i ett egenskapsvärde eller en relation.

Validering är särskilt viktigt om du skriver textmallar eller andra verktyg som bearbetar användarnas modeller. Validering säkerställer att modellerna uppfyller de förhandsvillkor som dessa verktyg förutsätter.

Varning

Du kan också tillåta att verifieringsbegränsningar definieras i separata tillägg till din DSL, tillsammans med tilläggsmenykommandon och gesthanterare. Användare kan välja att installera dessa tillägg utöver din DSL. Mer information finns i Utöka din DSL med hjälp av MEF.

Utföra validering

När en användare redigerar en modell, d.ex. en instans av ditt domänspecifika språk, kan följande åtgärder köra validering:

  • Högerklicka på diagrammet och välj Verifiera alla.

  • Högerklicka på den översta noden i Utforskaren för din DSL och välj Verifiera alla

  • Spara modellen.

  • Öppna modellen.

  • Dessutom kan du skriva programkod som kör validering, till exempel som en del av ett menykommando eller som svar på en ändring.

    Eventuella valideringsfel visas i fönstret Fellista . Användaren kan dubbelklicka på ett felmeddelande för att välja de modellelement som är orsaken till felet.

Definiera verifieringsbegränsningar

Du definierar verifieringsbegränsningar genom att lägga till valideringsmetoder i domänklasserna eller relationerna i din DSL. När valideringen körs, antingen av användaren eller under programkontroll, körs vissa eller alla valideringsmetoder. Varje metod tillämpas på varje instans av klassen och det kan finnas flera valideringsmetoder i varje klass.

Varje valideringsmetod rapporterar eventuella fel som hittas.

Anmärkning

Verifieringsmetoder rapporterar fel, men ändrar inte modellen. Om du vill justera eller förhindra vissa ändringar kan du läsa Alternativ till validering.

Definiera en verifieringsbegränsning

  1. Aktivera validering i noden Editor\Validation :

    1. Öppna Dsl\DslDefinition.dsl.

    2. I DSL Explorer expanderar du noden Redigerare och väljer Validering.

    3. I fönstret Egenskaper anger du egenskaperna Använder till true. Det är mest praktiskt att ange alla dessa egenskaper.

    4. Klicka på Transformera alla mallar i verktygsfältet i Solution Explorer .

  2. Skriv partiella klassdefinitioner för en eller flera av dina domänklasser eller domänrelationer. Skriv dessa definitioner i en ny kodfil i Dsl-projektet .

  3. Prefix för varje klass med det här attributet:

    [ValidationState(ValidationState.Enabled)]
    
    • Som standard aktiverar det här attributet även validering för härledda klasser. Om du vill inaktivera validering för en specifik härledd klass kan du använda ValidationState.Disabled.
  4. Lägg till valideringsmetoder i klasserna. Varje valideringsmetod kan ha valfritt namn, men har en parameter av typen ValidationContext.

    Det måste förses med ett eller flera ValidationMethod attribut.

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

    ValidationCategories anger när metoden körs.

    Till exempel:

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

Observera följande punkter om den här koden:

  • Du kan lägga till valideringsmetoder i domänklasser eller domänrelationer. Koden för dessa typer finns i Dsl\Generated Code\Domain*.cs.

  • Varje valideringsmetod tillämpas på varje instans av dess klass och dess underklasser. När det gäller en domänrelation är varje instans en länk mellan två modellelement.

  • Valideringsmetoder tillämpas inte i någon angiven ordning och varje metod tillämpas inte på instanserna av klassen i någon förutsägbar ordning.

  • Det är vanligtvis dåligt att en valideringsmetod uppdaterar butiksinnehållet, eftersom det skulle leda till inkonsekventa resultat. I stället bör metoden rapportera eventuella fel genom att anropa context.LogError, LogWarning eller LogInfo.

  • I LogError-anropet kan du ange en lista över modellelement eller relationslänkar som väljs när användaren dubbelklickar på felmeddelandet.

  • Information om hur du läser modellen i programkod finns i Navigera och uppdatera en modell i Programkod.

    Exemplet gäller för följande domänmodell. Relationen ParentsHaveChildren har roller som heter Barn och Förälder.

    DSL-definitionsdiagram – släktträdsmodell

Valideringskategorier

ValidationMethodAttribute I attributet anger du när verifieringsmetoden ska köras.

Kategori Execution
ValidationCategories När användaren anropar kommandot Verifiera meny.
ValidationCategories När modellfilen öppnas.
ValidationCategories När filen sparas. Om det finns valideringsfel får användaren möjlighet att avbryta spara-åtgärden.
ValidationCategories När filen sparas. Om det finns fel från metoder i den här kategorin varnas användaren för att det kanske inte går att öppna filen igen.

Använd den här kategorin för valideringsmetoder som testar för duplicerade namn eller ID:er eller andra villkor som kan orsaka inläsningsfel.
ValidationCategories När metoden ValidateCustom anropas. Valideringar i den här kategorin kan endast anropas från programkod.

Mer information finns i Anpassade valideringskategorier.

Var du ska placera valideringsmetoder

Du kan ofta uppnå samma effekt genom att placera en valideringsmetod på en annan typ. Du kan till exempel lägga till en metod i klassen Person i stället för relationen ParentsHaveChildren och få den iterera via länkarna:

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

Aggregera verifieringsbegränsningar. Om du vill tillämpa valideringen i en förutsägbar ordning definierar du en enskild valideringsmetod för en ägarklass, till exempel rotelementet i din modell. Med den här tekniken kan du också aggregera flera felrapporter i ett enda meddelande.

Nackdelar är att den kombinerade metoden är mindre lätt att hantera och att begränsningarna måste ha samma ValidationCategories. Vi rekommenderar därför att du behåller varje begränsning i en separat metod om möjligt.

Skicka värden i kontextcachen. Kontextparametern har en ordlista där du kan placera godtyckliga värden. Ordlistan fortsätter att finnas under valideringsprocessens varaktighet. En viss valideringsmetod kan till exempel behålla ett antal fel i kontexten och använda den för att undvika att överbelasta felfönstret med upprepade meddelanden. Till exempel:

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

Validering av multiplikiteter

Valideringsmetoder för att kontrollera minsta multiplicitet genereras automatiskt för din DSL. Koden skrivs till Dsl\Generated Code\MultiplicityValidation.cs. Dessa metoder träder i kraft när du aktiverar validering i noden Editor\Validation i DSL Explorer.

Om du anger multipliciteten för en roll för en domänrelation till 1..* eller 1...1, men användaren inte skapar en länk till den här relationen, visas ett verifieringsfelmeddelande.

Om din DSL till exempel har klasser Person och Stad och en relation PersonLivesInTown med en relation 1..\* i rollen Stad visas ett felmeddelande för varje person som inte har någon stad.

Köra validering från programkod

Du kan köra valideringen genom att komma åt eller skapa en ValidationController. Om du vill att felen ska visas för användaren i felfönstret använder du ValidationController som är kopplad till diagrammets DocData. Om du till exempel skriver ett menykommando CurrentDocData.ValidationController är det tillgängligt i klassen kommandouppsättning:

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

Mer information finns i Så här lägger du till ett kommando på snabbmenyn.

Du kan också skapa en separat valideringskontrollant och hantera felen själv. Till exempel:

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

Köra validering när en ändring sker

Om du vill se till att användaren varnas omedelbart om modellen blir ogiltig kan du definiera en butikshändelse som kör valideringen. Mer information om butikshändelser finns i Händelsehanterare sprider ändringar utanför modellen.

Utöver valideringskoden lägger du till en anpassad kodfil i ditt DslPackage-projekt , med innehåll som liknar följande exempel. Den här koden använder den ValidationController som är kopplad till dokumentet. Den här kontrollanten visar verifieringsfelen i Visual Studio-fellistan.

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

Hanterarna anropas också efter ångra- eller gör om-åtgärder som påverkar länkarna eller elementen.

Anpassade valideringskategorier

Förutom standardvalideringskategorierna, till exempel Meny och Öppna, kan du definiera dina egna kategorier. Du kan anropa dessa kategorier från programkoden. Användaren kan inte anropa dem direkt.

En vanlig användning för anpassade kategorier är att definiera en kategori som testar om modellen uppfyller förutsättningarna för ett visst verktyg.

Om du vill lägga till en valideringsmetod i en viss kategori prefixar du den med ett attribut som liknar detta:

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

Anmärkning

Du kan prefixa en metod med så många [ValidationMethod()] attribut som du vill. Du kan lägga till en metod i både anpassade kategorier och standardkategorier.

Så här anropar du en anpassad validering:


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

Alternativ till validering

Verifieringsbegränsningar rapporterar fel, men ändrar inte modellen. Om du i stället vill förhindra att modellen blir ogiltig kan du använda andra tekniker.

Dessa tekniker rekommenderas dock inte. Det är vanligtvis bättre att låta användaren bestämma hur en ogiltig modell ska korrigeras.

Justera ändringen för att återställa modellen till giltighet. Om användaren till exempel anger en egenskap över det tillåtna maxvärdet kan du återställa egenskapen till det maximala värdet. Det gör du genom att definiera en regel. Mer information finns i Regler som sprider ändringar i modellen.

Återställ transaktionen om en ogiltig ändring görs. Du kan också definiera en regel för det här ändamålet, men i vissa fall är det möjligt att åsidosätta en egenskapshanterare OnValueChanging(), eller åsidosätta en metod, till exempel OnDeleted(). För att återställa en transaktion, använd this.Store.TransactionManager.CurrentTransaction.Rollback(). Mer information finns i Ändra hanterare för domänegenskapsvärde.

Varning

Kontrollera att användaren vet att ändringen har justerats eller återställts. Använd till exempel System.Windows.Forms.MessageBox.Show("message").