Udostępnij za pośrednictwem


Sprawdzanie poprawności w języku specyficznym dla domeny

Jako autor języka specyficznego dla domeny (DSL) można zdefiniować ograniczenia walidacji, aby sprawdzić, czy model utworzony przez użytkownika ma znaczenie. Jeśli na przykład twój DSL umożliwia użytkownikom rysowanie drzewa rodzinnego ludzi i ich przodków, możesz napisać ograniczenie, które gwarantuje, że dzieci mają daty urodzenia po rodzicach.

Ograniczenia sprawdzania poprawności można wykonać po zapisaniu modelu, otwarciu i jawnym uruchomieniu polecenia menu Validate przez użytkownika. Walidację można również wykonać pod kontrolą programu. Na przykład można wykonać walidację w odpowiedzi na zmianę wartości właściwości lub relacji.

Walidacja jest szczególnie ważna, jeśli piszesz szablony tekstowe lub inne narzędzia, które przetwarzają modele użytkowników. Walidacja gwarantuje, że modele spełniają warunki wstępne przyjęte przez te narzędzia.

Ostrzeżenie

Można również zezwolić na definiowanie ograniczeń walidacji w osobnych rozszerzeniach dla rozszerzenia DSL wraz z poleceniami menu rozszerzeń i procedurami obsługi gestów. Użytkownicy mogą instalować te rozszerzenia oprócz rozszerzenia DSL. Aby uzyskać więcej informacji, zobacz Rozszerzanie dsL przy użyciu mef.

Uruchamianie walidacji

Gdy użytkownik edytuje model, czyli wystąpienie języka specyficznego dla domeny, następujące akcje mogą uruchomić walidację:

  • Kliknij prawym przyciskiem myszy diagram i wybierz polecenie Zweryfikuj wszystko.

  • Kliknij prawym przyciskiem myszy górny węzeł w Eksploratorze dsL i wybierz pozycję Zweryfikuj wszystko

  • Zapisz model.

  • Otwórz model.

  • Ponadto możesz napisać kod programu, który uruchamia walidację, na przykład jako część polecenia menu lub w odpowiedzi na zmianę.

    Wszelkie błędy walidacji zostaną wyświetlone w oknie Lista błędów. Użytkownik może kliknąć dwukrotnie komunikat o błędzie, aby wybrać elementy modelu, które są przyczyną błędu.

Definiowanie ograniczeń walidacji

Ograniczenia weryfikacji można zdefiniować przez dodanie metod weryfikacji do klas domen lub relacji dsl. Po uruchomieniu walidacji przez użytkownika lub pod kontrolą programu niektóre lub wszystkie metody walidacji są wykonywane. Każda metoda jest stosowana do każdego wystąpienia klasy i może istnieć kilka metod walidacji w każdej klasie.

Każda metoda walidacji zgłasza wszelkie znalezione błędy.

Uwaga

Metody walidacji zgłaszają błędy, ale nie zmieniają modelu. Jeśli chcesz dostosować lub zapobiec pewnym zmianom, zobacz Alternatywy do walidacji.

Aby zdefiniować ograniczenie weryfikacji

  1. Włącz walidację w węźle Editor\Validation :

    1. Otwórz plik Dsl\DslDefinition.dsl.

    2. W Eksploratorze DSL rozwiń węzeł Edytor i wybierz pozycję Walidacja.

    3. W okno Właściwości ustaw właściwości Uses na truewartość . Najwygodniejsze jest ustawienie wszystkich tych właściwości.

    4. Kliknij pozycję Przekształć wszystkie szablony na pasku narzędzi Eksplorator rozwiązań.

  2. Zapisuj częściowe definicje klas dla co najmniej jednej klasy domeny lub relacji domeny. Napisz te definicje w nowym pliku kodu w projekcie Dsl .

  3. Prefiks każdej klasy o tym atrybucie:

    [ValidationState(ValidationState.Enabled)]
    
    • Domyślnie ten atrybut włączy również walidację dla klas pochodnych. Jeśli chcesz wyłączyć walidację dla określonej klasy pochodnej, możesz użyć polecenia ValidationState.Disabled.
  4. Dodaj metody walidacji do klas. Każda metoda walidacji może mieć dowolną nazwę, ale ma jeden parametr typu ValidationContext.

    Musi być poprzedzony co najmniej jednym ValidationMethod atrybutem:

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

    Klasa ValidationCategories określa, kiedy jest wykonywana metoda.

    Na przykład:

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

Zwróć uwagę na następujące kwestie dotyczące tego kodu:

  • Metody walidacji można dodać do klas domeny lub relacji domeny. Kod dla tych typów znajduje się w pliku Dsl\Generated Code\Domain*.cs.

  • Każda metoda walidacji jest stosowana do każdego wystąpienia klasy i jej podklas. W przypadku relacji domeny każde wystąpienie jest połączeniem między dwoma elementami modelu.

  • Metody weryfikacji nie są stosowane w żadnej określonej kolejności, a każda metoda nie jest stosowana do wystąpień jej klasy w żadnej przewidywalnej kolejności.

  • Zazwyczaj nieprawidłową praktyką jest zaktualizowanie zawartości magazynu przez metodę weryfikacji, ponieważ może to prowadzić do niespójnych wyników. Zamiast tego metoda powinna zgłosić wszelkie błędy, wywołując context.LogErrormetodę , LogWarning lub LogInfo.

  • W wywołaniu LogError można podać listę elementów modelu lub linków relacji, które zostaną wybrane po dwukrotnym kliknięciu komunikatu o błędzie przez użytkownika.

  • Aby uzyskać informacje na temat sposobu odczytywania modelu w kodzie programu, zobacz Nawigowanie i aktualizowanie modelu w kodzie programu.

    Przykład dotyczy następującego modelu domeny. Relacja RodziceHaveChildren ma role o nazwie Child i Parent.

    DSL Definition diagram - family tree model

Kategorie weryfikacji

W atrybucie ValidationMethodAttribute należy określić, kiedy ma zostać wykonana metoda walidacji.

Kategoria Wykonanie
ValidationCategories Gdy użytkownik wywołuje polecenie menu Validate.
ValidationCategories Po otwarciu pliku modelu.
ValidationCategories Po zapisaniu pliku. Jeśli występują błędy walidacji, użytkownik otrzyma opcję anulowania operacji zapisywania.
ValidationCategories Po zapisaniu pliku. Jeśli występują błędy z metod w tej kategorii, użytkownik jest ostrzegany, że może nie być możliwe ponowne otwarcie pliku.

Ta kategoria służy do sprawdzania poprawności metod, które testować pod kątem zduplikowanych nazw lub identyfikatorów lub innych warunków, które mogą powodować błędy ładowania.
ValidationCategories Po wywołaniu metody ValidateCustom. Walidacje w tej kategorii mogą być wywoływane tylko z kodu programu.

Aby uzyskać więcej informacji, zobacz Niestandardowe kategorie weryfikacji.

Gdzie umieścić metody walidacji

Często można osiągnąć ten sam efekt, umieszczając metodę weryfikacji na innym typie. Na przykład można dodać metodę do klasy Person zamiast relacji ParentsHaveChildren i wykonać iterowanie za pośrednictwem linków:

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

Agregowanie ograniczeń weryfikacji. Aby zastosować walidację w przewidywalnej kolejności, zdefiniuj pojedynczą metodę weryfikacji w klasie właściciela, taką jak element główny modelu. Ta technika umożliwia również agregowanie wielu raportów o błędach w jeden komunikat.

Wady polegają na tym, że połączona metoda jest mniej łatwa do zarządzania i że ograniczenia muszą mieć taką samą wartość ValidationCategories. W związku z tym zalecamy zachowanie każdego ograniczenia w oddzielnej metodzie, jeśli jest to możliwe.

Przekazywanie wartości w pamięci podręcznej kontekstu. Parametr kontekstu zawiera słownik, w którym można umieścić dowolne wartości. Słownik utrzymuje się na czas wykonywania walidacji. Konkretna metoda sprawdzania poprawności może na przykład zachować liczbę błędów w kontekście i użyć jej, aby uniknąć zalewania okna błędu powtarzającymi się komunikatami. Na przykład:

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

Walidacja mnożenia

Metody weryfikacji sprawdzania minimalnej liczby są generowane automatycznie dla języka DSL. Kod jest zapisywany w pliku Dsl\Generated Code\MultiplicityValidation.cs. Te metody są stosowane po włączeniu walidacji w węźle Edytor\Walidacja w Eksploratorze DSL.

Jeśli ustawisz wielość roli relacji domeny na 1..*lub 1..1, ale użytkownik nie utworzy łącza tej relacji, zostanie wyświetlony komunikat o błędzie walidacji.

Jeśli na przykład twoje DSL ma klasy Person and Town i relację PersonLivesInTown z relacją 1..\* w roli Town, dla każdej osoby, która nie ma miasta, zostanie wyświetlony komunikat o błędzie.

Uruchamianie walidacji z poziomu kodu programu

Walidację można uruchomić, przechodząc do elementu ValidationController lub tworząc go. Jeśli chcesz, aby błędy są wyświetlane użytkownikowi w oknie błędu, użyj elementu ValidationController dołączonego do danych DocData diagramu. Jeśli na przykład piszesz polecenie menu, CurrentDocData.ValidationController jest dostępne w klasie zestawu poleceń:

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

Aby uzyskać więcej informacji, zobacz How to: Add a Command to the Shortcut Menu (Instrukcje: Dodawanie polecenia do menu skrótów).

Możesz również utworzyć oddzielny kontroler weryfikacji i samodzielnie zarządzać błędami. Na przykład:

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

Uruchamianie walidacji w przypadku wystąpienia zmiany

Jeśli chcesz upewnić się, że użytkownik jest natychmiast ostrzegany, jeśli model stanie się nieprawidłowy, możesz zdefiniować zdarzenie magazynu, które uruchamia walidację. Aby uzyskać więcej informacji na temat zdarzeń magazynu, zobacz Programy obsługi zdarzeń propagują zmiany poza modelem.

Oprócz kodu weryfikacyjnego dodaj niestandardowy plik kodu do projektu DslPackage z zawartością podobną do poniższego przykładu. Ten kod używa elementu ValidationController dołączonego do dokumentu. Ten kontroler wyświetla błędy walidacji na liście błędów programu 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);
    }
  }
}

Programy obsługi są również wywoływane po operacjach Cofnij lub Wykonaj ponownie, które wpływają na łącza lub elementy.

Niestandardowe kategorie walidacji

Oprócz standardowych kategorii weryfikacji, takich jak Menu i Otwórz, można zdefiniować własne kategorie. Możesz wywołać te kategorie z poziomu kodu programu. Użytkownik nie może wywołać ich bezpośrednio.

Typowym zastosowaniem dla kategorii niestandardowych jest zdefiniowanie kategorii, która sprawdza, czy model spełnia warunki wstępne określonego narzędzia.

Aby dodać metodę weryfikacji do określonej kategorii, należy ją prefiksować za pomocą atrybutu w następujący sposób:

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

Uwaga

Możesz prefiksować metodę z dowolną liczbą [ValidationMethod()] atrybutów. Możesz dodać metodę zarówno do kategorii niestandardowych, jak i standardowych.

Aby wywołać niestandardową walidację:


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

Alternatywy dla walidacji

Błędy raportów ograniczeń walidacji, ale nie zmieniają modelu. Jeśli zamiast tego chcesz zapobiec utracie ważności modelu, możesz użyć innych technik.

Jednak te techniki nie są zalecane. Zazwyczaj lepiej jest pozwolić użytkownikowi zdecydować, jak poprawić nieprawidłowy model.

Dostosuj zmianę, aby przywrócić model do ważności. Jeśli na przykład użytkownik ustawia właściwość powyżej dozwolonej wartości maksymalnej, możesz zresetować właściwość do maksymalnej wartości. W tym celu zdefiniuj regułę. Aby uzyskać więcej informacji, zobacz Reguły propagacji zmian w modelu.

Wycofaj transakcję, jeśli podjęto próbę nieprawidłowej zmiany. Można również zdefiniować regułę w tym celu, ale w niektórych przypadkach można zastąpić procedurę obsługi właściwości OnValueChanging()lub zastąpić metodę, taką jak OnDeleted(). Aby wycofać transakcję, użyj funkcji this.Store.TransactionManager.CurrentTransaction.Rollback(). Aby uzyskać więcej informacji, zobacz Procedury obsługi zmiany wartości właściwości domeny.

Ostrzeżenie

Upewnij się, że użytkownik wie, że zmiana została skorygowana lub wycofana. Na przykład użyj polecenia System.Windows.Forms.MessageBox.Show("message").