Comment : définir des contraintes de validation pour les modèles UML
Dans Visual Studio Ultimate, vous pouvez définir des contraintes de validation qui vérifient si le modèle correspond à une condition que vous spécifiez. Par exemple, vous pouvez définir une contrainte visant à vérifier qu'un utilisateur ne crée pas de boucle de relations d'héritage. La contrainte est appelée lorsque l'utilisateur tente d'ouvrir ou d'enregistrer le modèle ; elle peut également être appelée manuellement. Si la contrainte échoue, un message d'erreur que vous définissez est ajouté à la fenêtre d'erreur. Empaquetez ces contraintes dans une extension d'intégration Visual Studio (VSIX) et la distribuer à d'autres utilisateurs de Visual Studio Ultimate.
Vous pouvez également définir des contraintes qui valident le modèle par rapport à des ressources externes telles que des bases de données.
Notes
Si vous souhaitez valider du code de programme par rapport à un diagramme de couche, consultez Ajout d'une validation d'architecture personnalisée aux diagrammes de couche.
Configuration requise
Kit de développement Visual Studio, que vous pouvez obtenir depuis Galerie Visual Studio.
Kit de développement logiciel de visualisation et de modélisation de Visual Studio, que vous pouvez obtenir depuis Kit de développement logiciel de visualisation et de modélisation de Visual Studio sur la Galerie de code.
Application de contraintes de validation
Les contraintes de validation sont appliquées dans trois cas : lorsque vous enregistrez un modèle, lorsque vous ouvrez un modèle et lorsque vous cliquez sur Valider le modèle UML dans le menu Architecture. Dans chaque cas, seules les contraintes qui ont été définies pour ce cas seront appliquées, bien qu'en général vous définissiez chaque contrainte à appliquer dans plusieurs cas.
Les erreurs de validation sont signalées dans la fenêtre des erreurs Visual Studio et vous pouvez double-cliquer sur une erreur pour sélectionner les éléments de modèle qui figurent dans l'erreur.
Pour plus d'informations sur l'application de la validation, consultez Valider un modèle UML.
Définition d'une extension de validation
Pour créer une extension de validation pour un concepteur UML, vous devez créer une classe qui définisse les contraintes de validation, puis incorporer la classe à une extension d'intégration Visual Studio (VSIX). L'extension d'intégration Visual Studio (VSIX) joue le rôle d'un conteneur capable d'installer la contrainte. Il existe deux autres méthodes pour définir une extension de validation :
Créez une extension de validation dans son propre projet VSIX à l'aide d'un modèle de projet. Il s'agit de la méthode la plus rapide. Choisissez cette méthode si vous ne souhaitez pas combiner vos contraintes de validation avec d'autres types d'extensions, telles que les commandes de menu, les éléments de boîte à outils personnalisés ou les gestionnaires de mouvements. Vous pouvez définir plusieurs contraintes dans une même classe.
Créez séparément les classes de validation et les projets VSIX. Choisissez cette méthode si vous souhaitez combiner plusieurs types d'extensions au sein d'un même projet VSIX. Par exemple, si votre commande de menu prévoit que le modèle observe des contraintes spécifiques, vous pouvez l'incorporer au même projet VSIX en tant que méthode de validation.
Pour créer une extension de validation dans son propre projet VSIX
Dans la boîte de dialogue Nouveau projet, sous Modèles installés, sélectionnez Extension de validation.
Ouvrez le fichier .cs du nouveau projet et modifiez la classe pour implémenter votre contrainte de validation.
Pour plus d'informations, consultez Implémentation de la contrainte de validation.
Important
Vérifiez que vos fichiers .cs contiennent l'instruction using suivante :
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
Vous pouvez ajouter des contraintes supplémentaires en définissant de nouvelles méthodes. Pour identifier une méthode en tant que méthode de validation, cette dernière doit être marquée par les attributs de la même façon que la méthode de validation initiale.
Testez vos contraintes en appuyant sur F5. Pour plus d'informations, consultez Exécution de la validation.
Installez la commande de menu sur un autre ordinateur en copiant le fichier bin\*\*.vsix généré par votre projet. Pour plus d'informations, consultez Installation des contraintes de validation.
Lorsque vous ajoutez d'autres fichiers .cs, vous aurez normalement besoin des instructions using suivantes :
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.Classes;
Voici une procédure alternative :
Pour créer une contrainte de validation distincte dans un projet de bibliothèque de classes
Créez un projet de bibliothèque de classes en l'ajoutant à une solution VSIX existante ou en créant une solution.
Dans le menu Fichier, cliquez sur Nouveau, Projet.
Sous Modèles installés, développez Visual C# ou Visual Basic, puis dans la colonne centrale, cliquez sur Bibliothèque de classes.
Créez un projet VSIX, sauf si votre solution en comporte déjà un.
Dans Explorateur de solutions, dans le menu contextuel de la solution, choisissez Ajouter, Nouveau projet.
Sous Modèles installés, développez Visual C# ou Visual Basic, puis cliquez sur Extensibilité. Dans la colonne centrale, cliquez sur Projet VSIX .
Définissez le projet VSIX comme projet de démarrage de la solution.
- Dans l'Explorateur de solutions, dans le menu contextuel du projet VSIX testé, choisissez En faire un objet de démarrage.
Dans source.extension.vsixmanifest, sous Contenu, ajoutez un projet de bibliothèque de classes en tant que composant MEF.
Sous l'onglet Métadonnées, définissez un nom pour le package VSIX.
Sous l'onglet Installer les cibles, définissez Visual Studio Ultimate et Premium comme cibles.
Sous l'onglet Composants, choisissez Nouveau, et dans la boîte de dialogue, définissez :
Type = Composant MEF
Source = Projet dans la solution actuelle
Projet = Your class library project
Pour définir la classe de validation
Cette procédure n'est pas nécessaire si vous avez créé une classe de validation avec son propre projet VSIX à partir du modèle de projet de validation.
Dans le projet de classe de validation, ajoutez des références aux assemblys .NET suivants :
Microsoft.VisualStudio.Modeling.Sdk.12.0
Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml
Microsoft.VisualStudio.Uml.Interfaces
System.ComponentModel.Composition
Ajoutez un fichier au projet de bibliothèque de classes contenant du code semblable à l'exemple ci-dessous.
Chaque contrainte de validation est contenue dans une méthode qui est marquée avec un attribut spécifique. La méthode accepte un paramètre d'un type d'élément de modèle. Lorsque la validation est appelée, l'infrastructure de validation appliquera chaque méthode de validation à chaque élément de modèle conforme à son type de paramètre.
Vous pouvez placer ces méthodes dans toutes classes et espaces de noms. Modifiez-les à votre convenance.
using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using Microsoft.VisualStudio.Modeling.Validation; using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml; using Microsoft.VisualStudio.Uml.Classes; // You might also need the other Microsoft.VisualStudio.Uml namespaces. namespace Validation { public class MyValidationExtensions { // SAMPLE VALIDATION METHOD. // All validation methods have the following attributes. [Export(typeof(System.Action<ValidationContext, object>))] [ValidationMethod( ValidationCategories.Save | ValidationCategories.Open | ValidationCategories.Menu)] public void ValidateClassNames (ValidationContext context, // This type determines what elements // will be validated by this method: IClass elementToValidate) { // A validation method should not change the model. List<string> attributeNames = new List<string>(); foreach (IProperty attribute in elementToValidate.OwnedAttributes) { string name = attribute.Name; if (!string.IsNullOrEmpty(name) && attributeNames.Contains(name)) { context.LogError( string.Format("Duplicate attribute name '{0}' in class {1}", name, elementToValidate.Name), "001", elementToValidate); } attributeNames.Add(name); } } // Add more validation methods for different element types. } }
Exécution d'une contrainte de validation
À des fins de test, exécutez vos méthodes de validation en mode débogage.
Pour tester la contrainte de validation
Appuyez sur F5, ou, sur le menu Debug, sélectionnez Démarrer le Déboguage.
Une instance expérimentale de Visual Studio démarre alors.
Résolution des problèmes : Si un nouveau projet Visual Studio ne démarre pas :
Si vous avez plusieurs projets, assurez-vous que le projet VSIX est défini comme projet de démarrage de la solution.
Dans l'Explorateur de solutions, dans le menu contextuel du projet testé, choisissez Propriétés. Dans l'éditeur de propriétés du projet, cliquez sur l'onglet Déboguer. Assurez-vous que la chaîne présente dans le champ Démarrer le programme externe correspond au chemin d'accès complet de Visual Studio, généralement :
C:\Program Files\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe
Dans l'instance expérimentale de Visual Studio, ouvrez ou créez un projet de modélisation, puis ouvrez ou créez un diagramme de modélisation.
Pour configurer un test pour l'exemple de contrainte donné dans la section précédente :
Ouvrez un diagramme de classes.
Créez une classe et ajoutez-y deux attributs qui portent le même nom.
Dans le menu contextuel sur le diagramme, choisissez Valider.
Toutes erreurs figurant dans le modèle seront rapportées dans la fenêtre des erreurs.
Double-cliquez sur le rapport d'erreurs. Si les éléments mentionnés dans le rapport sont visibles à l'écran, ils seront mis en surbrillance.
Dépannage : si la commande Valider ne s'affiche pas dans le menu, vérifiez que :
Le projet de validation est répertorié en tant que composant MEF dans l'onglet Contenu de source.extensions.manifest dans le projet VSIX ;
les attributs Export et ValidationMethod appropriés sont joints aux méthodes de validation ;
ValidationCategories.Menu est inclus dans l'argument de l'attribut ValidationMethod et qu'il est composé d'autres valeurs grâce à l'opérateur logique OR (|) ;
les paramètres de tous les attributs Import et Export sont valides ;
Évaluation de la contrainte
La méthode de validation doit déterminer si la contrainte de validation que vous voulez appliquer a la valeur true ou false. Si la valeur true lui est affectée, cela ne doit avoir aucun effet. Si la valeur false lui est affectée, elle doit signaler une erreur à l'aide des méthodes fournies par le paramètre ValidationContext.
Notes
Les méthodes de validation ne doivent pas modifier le modèle.Il n'existe aucune garantie concernant le moment ou l'ordre d'exécution des contraintes.Si vous devez passer des informations entre des exécutions consécutives d'une méthode de validation lors d'une exécution de validation, vous pouvez utiliser le cache de contexte décrit dans Coordination de plusieurs validations.
Par exemple, pour garantir que chaque type (classe, interface ou énumérateur) ait un nom d'au moins trois caractères, vous pouvez utiliser la méthode suivante :
public void ValidateTypeName(ValidationContext context, IType type)
{
if (!string.IsNullOrEmpty(type.Name) && type.Name.Length < 3)
{
context.LogError(
string.Format("Type name {0} is too short", type.Name),
"001", type);
}
}
Pour plus d'informations sur les méthodes et les types que vous pouvez utiliser pour accéder au modèle et le lire, consultez Programmation à l'aide de l'API UML.
À propos des méthodes de contrainte de validation
Chaque contrainte de validation est définie par une méthode qui se présente comme suit :
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Save
| ValidationCategories.Menu
| ValidationCategories.Open)]
public void ValidateSomething
(ValidationContext context, IClassifier elementToValidate)
{...}
Les attributs et paramètres de chaque méthode de validation se présentent comme suit :
[Export(typeof(System.Action <ValidationContext, object>))] |
Définit la méthode comme une contrainte de validation à l'aide de Managed Extensibility Framework (MEF). |
[ValidationMethod (ValidationCategories.Menu)] |
Indique lorsqu'une validation va être effectuée. Utilisez des bits OR (|) si vous souhaitez combiner plusieurs options. Menu = appelé par le menu Valider. Save = appelé lors de l'enregistrement du modèle. Open = appelé lors de l'ouverture du modèle. Load = appelé lors de l'enregistrement du modèle mais, en cas d'infraction, prévient l'utilisateur du risque de ne pas pouvoir rouvrir le modèle. Également appelé lors du chargement, avant que le modèle ne soit analysé. |
public void ValidateSomething (ValidationContext context, IElement element) |
Remplacez le deuxième paramètre IElement par le type d'élément auquel vous souhaitez que la contrainte s'applique. La méthode de contrainte sera appelée sur tous les éléments du type spécifié. Le nom de la méthode n'est pas important. |
Vous pouvez définir autant de méthodes de validation que vous le souhaitez, avec des types différents dans le deuxième paramètre. Lorsque la validation est appelée, chaque méthode de validation sera appelée sur chaque élément de modèle conforme au type de paramètre.
Signalement d'erreurs de validation
Pour créer un rapport d'erreurs, utilisez les méthodes fournies par ValidationContext :
context.LogError("error string", errorCode, elementsWithError);
"error string" apparaît dans la liste d'erreurs de Visual Studio.
errorCode est une chaîne qui doit être un identificateur unique de l'erreur.
elementsWithError identifie des éléments du modèle. Lorsque l'utilisateur double-clique sur le rapport d'erreurs, la forme représentant cet élément est sélectionnée.
LogError(),LogWarning() et LogMessage() placent des messages dans les différentes sections de la liste d'erreurs.
Application des méthodes de validation
La validation est appliquée à chaque élément dans le modèle, y compris aux relations et aux parties de plus grands éléments, tels que les attributs d'une classe et les paramètres d'une opération.
Chaque méthode de validation est appliquée à chaque élément conforme au type dans son deuxième paramètre. Par exemple, cela signifie que si vous définissez une méthode de validation avec un deuxième paramètre IUseCase et une autre méthode avec son supertype IElement, les deux méthodes seront appliquées à chaque cas d'usage dans le modèle.
La hiérarchie des types est résumée dans Types d'éléments de modèles.
Vous pouvez également accéder aux éléments en suivant les relations. Par exemple, pour définir une méthode de validation sur IClass, vous pouvez lire en boucle ses propriétés détenues :
public void ValidateTypeName(ValidationContext context, IClass c)
{
foreach (IProperty property in c.OwnedAttributes)
{
if (property.Name.Length < 3)
{
context.LogError(
string.Format(
"Property name {0} is too short",
property.Name),
"001", property);
}
}
}
Création d'une méthode de validation sur le modèle
Pour garantir qu'une méthode de validation est appelée une seule fois pendant chaque exécution de validation, vous pouvez valider l'IModel :
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs; ...
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu)]
public void ValidateModel(ValidationContext context, IModel model)
{ foreach (IElement element in model.OwnedElements)
{ ...
Validation de formes et de diagrammes
Les méthodes de validation ne sont pas appelées sur les éléments d'affichage tels que les diagrammes et les formes, parce que l'objectif premier des méthodes de validation consiste à valider le modèle. En revanche, vous pouvez accéder au diagramme actuel à l'aide du contexte de diagramme.
Dans votre classe de validation, déclarez DiagramContext en tant que propriété importée :
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
...
[Import]
public IDiagramContext DiagramContext { get; set; }
Dans une méthode de validation, vous pouvez utiliser DiagramContext pour accéder au diagramme de focus actuel, le cas échéant :
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu)]
public void ValidateModel(ValidationContext context, IModel model)
{
IDiagram focusDiagram = DiagramContext.CurrentDiagram;
if (focusDiagram != null)
{
foreach (IShape<IUseCase> useCaseShape in
focusDiagram.GetChildShapes<IUseCase>())
{ ...
Pour consigner une erreur, vous devez obtenir l'élément de modèle que la forme représente, parce que vous ne pouvez pas passer une forme à LogError :
IUseCase useCase = useCaseShape.Element;
context.LogError(... , usecase);
Coordination de plusieurs validations
Lorsque la validation est appelée (par exemple, par l'utilisateur à partir d'un menu de diagramme), chaque méthode de validation est appliquée à chaque élément de modèle. Cela signifie que, lors d'un appel unique de l'infrastructure de validation, la même méthode peut être appliquée de nombreuses fois à différents éléments.
Cela pose un problème pour les validations associées aux relations entre des éléments. Par exemple, vous pouvez écrire une validation qui commence à partir d'un cas d'usage et qui parcourt les relations include afin de vérifier qu'il n'y a pas de boucle. Cependant, lorsque la méthode est appliquée à chaque cas d'usage d'un modèle qui dispose de nombreux liens include, il est probable qu'elle traite de manière répétée les mêmes zones du modèle.
Pour éviter ce type de situation, il existe un cache de contexte dans lequel les informations sont conservées pendant une exécution de validation. Vous pouvez l'utiliser pour passer les informations entre les différentes exécutions des méthodes de validation. Par exemple, vous pouvez stocker une liste des éléments qui ont déjà été traités lors de cette exécution de validation. Le cache est créé au début de chaque exécution de validation et ne peut pas être utilisé pour passer les informations entre différentes exécutions de validation.
context.SetCacheValue<T> (name, value) |
Stockez une valeur. |
context.TryGetCacheValue<T> (name, out value) |
Obtenez une valeur. En cas de succès, retourne la valeur true. |
context.GetValue<T>(name) |
Obtenez une valeur. |
Context.GetValue<T>() |
Obtenez une valeur du type spécifié. |
Installation et désinstallation d'une extension
Vous pouvez installer une extension Visual Studio sur votre propre ordinateur et sur d'autres.
Pour installer une extension
Sur votre ordinateur, recherchez le fichier .vsix qui a été généré par votre projet VSIX.
Dans Explorateur de solutions, dans le menu contextuel du projet VSIX, choisissez Ouvrir le dossier dans l'Explorateur Windows.
Localisez le fichier bin\*\YourProject.vsix
Copiez le fichier .vsix sur l'ordinateur cible sur lequel vous souhaitez installer l'extension. Il peut s'agir de votre propre ordinateur ou d'un autre.
- L'ordinateur cible doit disposer de l'une des éditions de Visual Studio que vous avez spécifiées dans source.extension.vsixmanifest.
Sur l'ordinateur cible, ouvrez le fichier .vsix.
Le Programme d'installation des extensions Visual Studio ouvre et installe l'extension.
Démarrez ou redémarrez Visual Studio.
Pour désinstaller une extension
Dans le menu Outils, cliquez sur Gestionnaire d'extensions....
Développez Extensions installées.
Sélectionnez l'extension, puis cliquez sur Désinstaller.
Exceptionnellement, une extension défaillante ne parvient pas à se charger et crée un rapport dans la fenêtre d'erreur, mais ne s'affiche pas dans le gestionnaire d'extensions. Dans ce cas, supprimez l'extension en supprimant le fichier de l'emplacement suivant, où %LocalAppData% est généralement au format DriveName:\Users\UserName\AppData\Local:
%LocalAppData%\Microsoft\VisualStudio\12.0\Extensions
Exemple
Cet exemple recherche des boucles dans la relation de dépendance entre plusieurs éléments.
Il procédera à la validation à l'aide des commandes de menu d'enregistrement et de validation.
/// <summary>
/// Verify that there are no loops in the dependency relationsips.
/// In our project, no element should be a dependent of itself.
/// </summary>
/// <param name="context">Validation context for logs.</param>
/// <param name="element">Element to start validation from.</param>
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu
| ValidationCategories.Save | ValidationCategories.Open)]
public void NoDependencyLoops(ValidationContext context, INamedElement element)
{
// The validation framework will call this method
// for every element in the model. But when we follow
// the dependencies from one element, we will validate others.
// So we keep a list of the elements that we don't need to validate again.
// The list is kept in the context cache so that it is passed
// from one execution of this method to another.
List<INamedElement> alreadySeen = null;
if (!context.TryGetCacheValue("No dependency loops", out alreadySeen))
{
alreadySeen = new List<INamedElement>();
context.SetCacheValue("No dependency loops", alreadySeen);
}
NoDependencyLoops(context, element,
new INamedElement[0], alreadySeen);
}
/// <summary>
/// Log an error if there is any loop in the dependency relationship.
/// </summary>
/// <param name="context">Validation context for logs.</param>
/// <param name="element">The element to be validated.</param>
/// <param name="dependants">Elements we've followed in this recursion.</param>
/// <param name="alreadySeen">Elements that have already been validated.</param>
/// <returns>true if no error was detected</returns>
private bool NoDependencyLoops(ValidationContext context,
INamedElement element, INamedElement[] dependants,
List<INamedElement> alreadySeen)
{
if (dependants.Contains(element))
{
context.LogError(string.Format("{0} should not depend on itself", element.Name),
"Fabrikam.UML.NoGenLoops", // unique code for this error
dependants.SkipWhile(e => e != element).ToArray());
// highlight elements that are in the loop
return false;
}
INamedElement[] dependantsPlusElement =
new INamedElement[dependants.Length + 1];
dependants.CopyTo(dependantsPlusElement, 0);
dependantsPlusElement[dependantsPlusElement.Length - 1] = element;
if (alreadySeen.Contains(element))
{
// We have already validated this when we started
// from another element during this validation run.
return true;
}
alreadySeen.Add(element);
foreach (INamedElement supplier in element.GetDependencySuppliers())
{
if (!NoDependencyLoops(context, supplier,
dependantsPlusElement, alreadySeen))
return false;
}
return true;
}
Voir aussi
Concepts
Comment : définir et installer une extension de modélisation