Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
En tant qu’auteur d’un langage spécifique au domaine (DSL), vous pouvez définir des contraintes de validation pour vérifier que le modèle créé par l’utilisateur est significatif. Par exemple, si votre DSL permet aux utilisateurs de dessiner un arbre familial de personnes et de leurs ancêtres, vous pouvez écrire une contrainte qui garantit que les enfants ont des dates de naissance après leurs parents.
Vous pouvez exécuter les contraintes de validation lorsque le modèle est enregistré, lorsqu’il est ouvert et lorsque l’utilisateur exécute explicitement la commande Valider le menu. Vous pouvez également exécuter la validation sous contrôle de programme. Par exemple, vous pouvez exécuter la validation en réponse à une modification d’une valeur ou d’une relation de propriété.
La validation est particulièrement importante si vous écrivez des modèles de texte ou d’autres outils qui traitent les modèles de vos utilisateurs. La validation garantit que les modèles remplissent les conditions préalables supposées par ces outils.
Avertissement
Vous pouvez également autoriser la définition de contraintes de validation dans des extensions distinctes de votre DSL, ainsi que des commandes de menu d’extension et des gestionnaires de mouvements. Les utilisateurs peuvent choisir d’installer ces extensions en plus de votre DSL. Pour plus d’informations, consultez Étendre votre DSL à l’aide de MEF.
Exécution de la validation
Lorsqu’un utilisateur modifie un modèle, c’est-à-dire une instance de votre langage spécifique au domaine, les actions suivantes peuvent exécuter la validation :
Cliquez avec le bouton droit sur le diagramme et sélectionnez Valider tout.
Cliquez avec le bouton droit sur le nœud supérieur dans l’Explorateur de votre DSL, puis sélectionnez Valider tout
Enregistrez le modèle.
Ouvrez le modèle.
En outre, vous pouvez écrire du code de programme qui exécute la validation, par exemple, dans le cadre d’une commande de menu ou en réponse à une modification.
Toutes les erreurs de validation s’affichent dans la fenêtre Liste d’erreurs . L’utilisateur peut double-cliquer sur un message d’erreur pour sélectionner les éléments de modèle qui sont la cause de l’erreur.
Définition des contraintes de validation
Vous définissez des contraintes de validation en ajoutant des méthodes de validation aux classes de domaine ou aux relations de votre DSL. Lorsque la validation est exécutée, par l’utilisateur ou sous contrôle de programme, certaines ou toutes les méthodes de validation sont exécutées. Chaque méthode est appliquée à chaque instance de sa classe, et il peut y avoir plusieurs méthodes de validation dans chaque classe.
Chaque méthode de validation signale toutes les erreurs qu’elle trouve.
Note
Les méthodes de validation signalent des erreurs, mais ne modifient pas le modèle. Si vous souhaitez ajuster ou empêcher certaines modifications, consultez Alternatives à la validation.
Pour définir une contrainte de validation
Activez la validation dans le nœud Éditeur\Validation :
Ouvrez Dsl\DslDefinition.dsl.
Dans l’Explorateur DSL, développez le nœud Éditeur et sélectionnez Validation.
Dans la fenêtre Propriétés, définissez les propriétés Use sur
true. Il est plus pratique de définir toutes ces propriétés.Cliquez sur Transformer tous les modèles dans la barre d’outils de l’Explorateur de solutions .
Écrivez des définitions de classes partielles pour une ou plusieurs de vos classes de domaine ou relations de domaine. Écrivez ces définitions dans un nouveau fichier de code dans le projet Dsl .
Préfixez chaque classe avec cet attribut :
[ValidationState(ValidationState.Enabled)]- Par défaut, cet attribut active également la validation pour les classes dérivées. Si vous souhaitez désactiver la validation pour une classe dérivée spécifique, vous pouvez utiliser
ValidationState.Disabled.
- Par défaut, cet attribut active également la validation pour les classes dérivées. Si vous souhaitez désactiver la validation pour une classe dérivée spécifique, vous pouvez utiliser
Ajoutez des méthodes de validation aux classes. Chaque méthode de validation peut avoir n’importe quel nom, mais avoir un paramètre de type ValidationContext.
Il doit être précédé d’un ou plusieurs
ValidationMethodattributs :[ValidationMethod (ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ]Les ValidationCategories spécifient quand la méthode est exécutée.
Par exemple:
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);
}
}
Notez les points suivants concernant ce code :
Vous pouvez ajouter des méthodes de validation aux classes de domaine ou aux relations de domaine. Le code de ces types se trouve dans Dsl\Generated Code\Domain*.cs.
Chaque méthode de validation est appliquée à chaque instance de sa classe et à ses sous-classes. Dans le cas d’une relation de domaine, chaque instance est un lien entre deux éléments de modèle.
Les méthodes de validation ne sont appliquées dans aucun ordre spécifié, et chaque méthode n’est pas appliquée aux instances de sa classe dans un ordre prévisible.
Il est généralement incorrect pour une méthode de validation de mettre à jour le contenu du magasin, car cela entraînerait des résultats incohérents. Au lieu de cela, la méthode doit signaler toute erreur en appelant
context.LogError,LogWarningouLogInfo.Dans l’appel LogError, vous pouvez fournir une liste d’éléments de modèle ou de liens de relation qui seront sélectionnés lorsque l’utilisateur double-clique sur le message d’erreur.
Pour plus d’informations sur la lecture du modèle dans le code du programme, consultez Navigation et mise à jour d’un modèle dans le code du programme.
L’exemple s’applique au modèle de domaine suivant. La relation ParentsHaveChildren a des rôles nommés Enfant et Parent.
Catégories de validation
Dans l’attribut ValidationMethodAttribute , vous spécifiez quand la méthode de validation doit être exécutée.
| Catégorie | Execution |
|---|---|
| ValidationCategories | Lorsque l’utilisateur appelle la commande Valider le menu. |
| ValidationCategories | Lorsque le fichier de modèle est ouvert. |
| ValidationCategories | Lorsque le fichier est enregistré. En cas d’erreurs de validation, l’utilisateur aura la possibilité d’annuler l’opération d’enregistrement. |
| ValidationCategories | Lorsque le fichier est enregistré. S’il existe des erreurs provenant de méthodes de cette catégorie, l’utilisateur est averti qu’il peut ne pas être possible de rouvrir le fichier. Utilisez cette catégorie pour les méthodes de validation qui testent les noms ou ID dupliqués, ou d’autres conditions susceptibles de provoquer des erreurs de chargement. |
| ValidationCategories | Lorsque la méthode ValidateCustom est appelée. Les validations de cette catégorie peuvent être appelées uniquement à partir du code du programme. Pour plus d’informations, consultez Catégories de validation personnalisées. |
Où placer des méthodes de validation
Vous pouvez souvent obtenir le même effet en plaçant une méthode de validation sur un type différent. Par exemple, vous pouvez ajouter une méthode à la classe Person au lieu de la relation ParentsHaveChildren et effectuer une itération dans les liens :
[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)
{ ...
Agrégation des contraintes de validation. Pour appliquer la validation dans un ordre prévisible, définissez une méthode de validation unique sur une classe propriétaire, par exemple l’élément racine de votre modèle. Cette technique vous permet également d’agréger plusieurs rapports d’erreurs dans un seul message.
Les inconvénients sont que la méthode combinée est moins facile à gérer, et que les contraintes doivent toutes avoir la même ValidationCategories. Nous vous recommandons donc de conserver chaque contrainte dans une méthode distincte si possible.
Passage de valeurs dans le cache de contexte. Le paramètre de contexte a un dictionnaire dans lequel vous pouvez placer des valeurs arbitraires. Le dictionnaire persiste pendant toute la durée du processus de validation. Une méthode de validation particulière peut, par exemple, conserver un nombre d’erreurs dans le contexte et l’utiliser pour éviter d’inonder la fenêtre d’erreur avec des messages répétés. Par exemple:
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( ... ); }
Validation des multiplicités
Les méthodes de validation pour la vérification de la multiplicité minimale sont générées automatiquement pour votre DSL. Le code est écrit dans Dsl\Generated Code\MultiplicityValidation.cs. Ces méthodes prennent effet lorsque vous activez la validation dans le nœud Éditeur\Validation dans l’Explorateur DSL.
Si vous définissez la multiplicité d’un rôle d’une relation de domaine sur 1..* ou 1..1, mais que l’utilisateur ne crée pas de lien de cette relation, un message d’erreur de validation s’affiche.
Par exemple, si votre DSL possède des classes Person et Town, et une relation PersonLivesInTown avec une relation 1..\* au rôle Ville, alors pour chaque personne qui n’a pas de ville, un message d’erreur s’affiche.
Exécution de la validation à partir du code du programme
Vous pouvez exécuter la validation en accédant ou en créant un ValidationController. Si vous souhaitez que les erreurs s’affichent à l’utilisateur dans la fenêtre d’erreur, utilisez le contrôle ValidationController attaché à la docData de votre diagramme. Par exemple, si vous écrivez une commande de menu, CurrentDocData.ValidationController est disponible dans la classe de jeu de commandes :
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;
...
Pour plus d’informations, consultez Guide pratique pour ajouter une commande au menu contextuel.
Vous pouvez également créer un contrôleur de validation distinct et gérer vous-même les erreurs. Par exemple:
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) { ... }
}
Exécution de la validation lorsqu’une modification se produit
Si vous souhaitez vous assurer que l’utilisateur est averti immédiatement si le modèle devient non valide, vous pouvez définir un événement de magasin qui exécute la validation. Pour plus d’informations sur les événements du magasin, consultez Les gestionnaires d’événements propagent les modifications en dehors du modèle.
En plus du code de validation, ajoutez un fichier de code personnalisé à votre projet DslPackage , avec du contenu similaire à l’exemple suivant. Ce code utilise celui ValidationController qui est attaché au document. Ce contrôleur affiche les erreurs de validation dans la liste d’erreurs 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);
}
}
}
Les gestionnaires sont également appelés après les opérations Annuler ou Rétablir qui affectent les liens ou les éléments.
Catégories de validation personnalisées
En plus des catégories de validation standard, telles que Menu et Ouvrir, vous pouvez définir vos propres catégories. Vous pouvez appeler ces catégories à partir du code du programme. L’utilisateur ne peut pas les appeler directement.
Une utilisation classique pour les catégories personnalisées consiste à définir une catégorie qui teste si le modèle satisfait aux conditions préalables d’un outil particulier.
Pour ajouter une méthode de validation à une catégorie particulière, préfixez-la avec un attribut comme suit :
[ValidationMethod(CustomCategory = "PreconditionsForGeneratePartsList")]
[ValidationMethod(ValidationCategory.Menu)]
private void TestForCircularLinks(ValidationContext context)
{...}
Note
Vous pouvez préfixer une méthode avec autant d’attributs [ValidationMethod()] que vous le souhaitez. Vous pouvez ajouter une méthode aux catégories personnalisées et standard.
Pour appeler une validation personnalisée :
// Invoke all validation methods in a custom category:
validationController.ValidateCustom
(store, // or a list of model elements
"PreconditionsForGeneratePartsList");
Alternatives à la validation
Les contraintes de validation signalent des erreurs, mais ne modifient pas le modèle. Si, au lieu de cela, vous souhaitez empêcher le modèle de devenir non valide, vous pouvez utiliser d’autres techniques.
Toutefois, ces techniques ne sont pas recommandées. Il est généralement préférable de laisser l’utilisateur décider comment corriger un modèle non valide.
Ajustez la modification pour rétablir la validité du modèle. Par exemple, si l’utilisateur définit une propriété au-dessus du maximum autorisé, vous pouvez réinitialiser la propriété à la valeur maximale. Pour ce faire, définissez une règle. Pour plus d’informations, consultez Comment les règles propagent des modifications dans le modèle.
Restaurez la transaction si une modification non valide est tentée. Vous pouvez également définir une règle à cet effet, mais dans certains cas, il est possible de remplacer un gestionnaire de propriétés OnValueChanging() ou de remplacer une méthode telle que OnDeleted(). Pour restaurer une transaction, utilisez this.Store.TransactionManager.CurrentTransaction.Rollback(). Pour plus d’informations, voir Gestionnaires de modification de valeur de propriété de domaine.
Avertissement
Assurez-vous que l’utilisateur sait que la modification a été ajustée ou restaurée. Par exemple, utilisez System.Windows.Forms.MessageBox.Show("message").