如何:为 UML 模型定义验证约束
在 Visual Studio 旗舰版中,可以定义用于测试模型是否满足指定条件的验证约束。 例如,您可以定义一个约束来确保用户不创建继承关系的循环。 当用户尝试打开或保存模型时将会调用该约束,也可以手动调用该约束。 如果约束失败,则您定义的错误消息将添加到错误窗口中。 可以将这些约束打包到 Visual Studio 集成扩展 (VSIX) 中,并将其分发给其他 Visual Studio 旗舰版用户。
还可以定义根据外部资源(如数据库)验证模型的约束。
备注
若要对照层关系图验证程序代码,请参见向层关系图添加自定义体系结构验证。
要求
Visual Studio SDK,您可以从 Visual Studio库获取。
Visual Studio可视化和建模SDK,则可以从 Visual Studio在代码库中的可视化和建模SDK获取。
应用验证约束
在三种情况下应用验证约束:在保存模型时;在打开模型时;在**“体系结构”菜单上单击“验证 UML 模型”**时。 对于每一种情况,只能应用已为该情况定义的那些约束,尽管通常会将每种约束定义为可在多种情况下应用。
Visual Studio 错误窗口中会报告验证错误,可以双击错误以选择出错的模型元素。
有关应用验证的更多信息,请参见验证 UML 模型。
定义验证扩展
若要为 UML 设计器创建验证扩展,您必须创建一个定义验证约束的类,并将此类嵌入 Visual Studio 集成扩展 (VSIX) 中。 VSIX 将用作可安装约束的容器。 有两种用于定义验证扩展的替代方法:
**使用项目模板在验证扩展的 VSIX 中创建它。**这是一种快速方法。 如果您不想将验证约束与其他类型的扩展(如菜单命令、自定义工具箱项或笔势处理程序)合并,则使用此方法。 可以在一个类中定义多个约束。
**创建单独的验证类和 VSIX 项目。**如果将多个类型的扩展并入同一个 VSIX 中,则请使用此方法。 例如,如果您的菜单命令需要模型遵守特定约束,则可以将菜单命令嵌入到验证方法所在的 VSIX 中。
在验证扩展自己的 VSIX 中创建它
在**“新建项目”对话框中的“建模项目”下,选择“验证扩展”**。
在新项目中打开 .cs 文件,并修改该类以实现验证约束。
有关更多信息,请参见实现验证约束。
重要
确保 .cs 文件包含下列 using 语句:
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
可以通过定义新的方法来添加其他约束。 若要将一个方法标识为验证方法,则必须使用特性按照标记初始验证方法的方式来标记该方法。
按 F5 测试约束。 有关更多信息,请参见执行验证。
通过复制由您的项目生成的 bin\*\*.vsix 文件,在其他计算机上安装菜单命令。 有关更多信息,请参见安装验证约束。
在添加其他 .cs 文件时,通常需要下列 using 语句:
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;
这是一种替代方法:
在类库项目中创建单独的验证约束
创建一个类库项目(通过在现有 VSIX 解决方案中添加一个类库项目或创建一个新的解决方案)。
在**“文件”菜单上,选择“新建”和“项目”**。
在 ***** 安装的模板 *****下,展开 *** visual C# *** 或 *** Visual Basic ***,在中间列然后选择 *** 选件类库 ***。
除非您的解决方案中已有一个,请创建一个VSIX项目:
在 *** 解决方案资源管理器 ***,解决方案中的快捷菜单上,选择 *** 添加 ***,*** 新项目 ***。
在 *** 安装的模板 ***下,展开 *** visual C# *** 或 *** Visual Basic ***,然后选择 *** 扩展性 ***。 在中间列中,单击“VSIX 项目”。
将 VSIX 项目设置为解决方案的启动项目。
- 在解决方案资源管理器中,在VSIX项目的快捷菜单中选择 *** 设置为启动项目 ***。
在 source.extension.vsixmanifest,在 ***** 内容 *****下,添加选件类库项目添加为MEF组件:
在 *** 元数据 *** 选项卡上,将一个名称VSIX中。
在 *** 安装目标 *** 选项卡上,将Visual Studio最终和高质量作为目标。
在 *** 属性 *** 选项卡中,选择 新建,然后在对话框中,设置:
*** 类型 *** = *** MEF组件 ***
源 = *** 在当前解决方案中的项目 ***
项目 = 您的选件类库项目
定义验证类
如果您已使用其自己的 VSIX 从验证项目模板创建验证类,则无需此过程。
在验证类项目中,添加对以下 .NET 程序集的引用:
Microsoft.VisualStudio.Modeling.Sdk.11.0
Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml
Microsoft.VisualStudio.Uml.Interfaces
System.ComponentModel.Composition
将一个文件添加到类库项目中,其中包含与以下示例类似的代码。
每个验证约束均包含在用特定特性标记的方法中。 该方法接受模型元素类型的参数。 调用验证时,验证框架将对符合其参数类型的每个模型元素应用每种验证方法。
您可以将这些方法置于任何类和命名空间中。 将它们变成您的首选项。
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. } }
执行验证约束
仅为测试目的在调试模式下执行验证方法。
测试验证约束
按 *** F5 ***,或者在 *** 调试 *** 菜单上,选择 *** 开始调试 ***。
这将启动 Visual Studio 的实验实例。
疑难解答:如果新的 Visual Studio 未启动:
如果您有多个项目,请确保将 VSIX 项目设置为解决方案的启动项目。
在解决方案资源管理器中,该启动或项目的快捷菜单仅,选择 属性。 在项目属性编辑器中,选择 *** 调试 *** 选项。 确保**“启动外部程序”**字段中的字符串是 Visual Studio 的完整路径名,通常为:
C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe
在实验性 Visual Studio 中,打开或创建一个建模项目,然后打开或创建一个建模图。
为上一节中给定的示例约束设置测试:
打开类关系图。
创建一个类,并添加具有同一名称的两个特性。
在快捷菜单上关系图上的任意位置,请选择 *** 验证 ***。
错误窗口中将报告模型中的任何错误。
双击该错误报告。 如果报告中提及的元素出现在屏幕上,则会突出显示。
疑难解答:如果**“验证”**命令没有出现在菜单中,请确保:
验证项目列表为 *** 属性 *** 选项的一个MEF组件在VSIX项目的 source.extensions.manifest。
正确的 Export 和 ValidationMethod 特性会附加到验证方法。
ValidationCategories.Menu 包含在 ValidationMethod 特性的参数中,并且它是由使用逻辑“或”(|) 的其他值构成的。
所有 Import 和 Export 特性的参数都有效。
评估约束
验证方法应确定需要应用的验证约束为 true 还是 false。 如果为 true,则不应执行任何操作。 如果为 false,则应使用 ValidationContext 参数提供的方法报告错误。
备注
验证方法不应更改模型。无法保证将在何时按何顺序执行约束。运行某个验证时,如果必须在验证方法的连续执行之间传递信息,则可使用协调多个验证中所述的上下文缓存。
例如,如果需要确保每个类型(类、接口或枚举器)具有的名称的长度至少为 3 个字符,则可以使用此方法:
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);
}
}
有关可用于导航和读取模型的方法和类型的信息,请参见使用 UML API 编程。
关于验证约束方法
每个验证约束都由采用以下格式的方法定义:
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Save
| ValidationCategories.Menu
| ValidationCategories.Open)]
public void ValidateSomething
(ValidationContext context, IClassifier elementToValidate)
{...}
每个验证方法的特性和参数如下所示:
[Export(typeof(System.Action <ValidationContext, object>))] |
使用托管扩展性框架 (MEF) 将该方法定义为验证约束。 |
[ValidationMethod (ValidationCategories.Menu)] |
指定何时将执行验证。 如果要组合多个选项,请使用按位“或”(|)。 Menu = 由“验证”菜单调用。 Save = 在保存模型时调用。 Open = 在打开模型时调用。 Load = 在保存模型时调用,但若出现冲突,则警告用户可能无法重新打开模型。 也在加载时调用,但会在分析模型之前。 |
public void ValidateSomething (ValidationContext context, IElement element) |
将第二个参数 IElement 替换为要将约束应用于的元素类型。 将对指定类型的所有元素调用约束方法。 方法的名称并不重要。 |
可以在第二个参数中使用不同类型来定义所需数量的验证方法。 调用验证时,将对每个符合参数类型的模型元素调用每种验证方法。
报告验证错误
若要创建一个错误报告,请使用 ValidationContext 提供的方法:
context.LogError("error string", errorCode, elementsWithError);
"error string" 显示在 Visual Studio 错误列表中
errorCode 是一个字符串,它应为错误的唯一标识符
elementsWithError 标识模型中的元素。 当用户双击错误报告时,表示此元素的形状将被选中。
LogError(), LogWarning() 和 LogMessage() 将消息放入错误列表的不同部分。
如何应用验证方法
对模型中的每个元素应用验证,包括更大元素的关系和各个部分,例如类的特性和操作的参数。
对符合其第二个参数中的类型的每个元素应用每种验证方法。 这意味着,如果使用 IUseCase 的第二个参数定义一种验证方法,并使用其超类型 IElement 定义另一种验证方法,则将对模型中的每个用例应用这两种方法。
模型元素类型中汇总了类型的层次结构。
也可以按下列关系访问元素。 例如,如果您要对 IClass 定义验证方法,则可以遍历其拥有的属性:
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);
}
}
}
在模型上创建验证方法
如果您需要确保验证方法在每个验证运行期间刚好被调用一次,则可以验证 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)
{ ...
验证形状和关系图
由于验证方法的主要用途是验证模型,因此不会对显示元素(如关系图和形状)调用验证方法。 不过,您可以使用关系图上下文来访问当前关系图。
在您的验证类内,将 DiagramContext 声明为导入的属性:
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
...
[Import]
public IDiagramContext DiagramContext { get; set; }
在验证方法中,可以使用 DiagramContext 访问当前焦点关系图(如果有):
[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>())
{ ...
若要记录错误,您必须获取形状表示的模型元素,因为无法将形状传递给 LogError:
IUseCase useCase = useCaseShape.Element;
context.LogError(... , usecase);
协调多个验证
调用验证时(例如用户从关系图菜单调用),会对每个模型元素应用每种验证方法。 这意味着在验证框架的单次调用中,可能会对不同元素应用同一方法很多次。
这会给处理元素间关系的验证带来问题。 例如,您可能会编写一个验证,该验证从一个用例开始,然后遍历 include 关系来验证不存在循环。 但是,如果该方法应用于具有很多 include 链接的模型中的每个用例时,很有可能会重复处理模型的相同区域。
为了避免这种情况,提供了一个用于在验证运行期间在其中保存信息的上下文缓存。 可使用该缓存在验证方法的不同执行之间传递信息。 例如,您可以存储此次验证运行中已处理的元素列表。 该缓存在每次验证运行开始时创建,不能用于在不同的验证运行之间传递信息。
context.SetCacheValue<T> (name, value) |
存储值 |
context.TryGetCacheValue<T> (name, out value) |
获取值。 如果成功,则返回 true。 |
context.GetValue<T>(name) |
获取值。 |
Context.GetValue<T>() |
获取指定类型的值。 |
安装和卸载扩展
既可以在您自己的计算机上也可以在其他计算机上安装 Visual Studio 扩展。
安装扩展
在您的计算机中查找由 VSIX 项目生成的 .vsix 文件。
在 *** 解决方案资源管理器 ***,在VSIX项目的快捷菜单上,选择 *** 打开在Windows资源管理器中的文件夹 ***。
找到文件 bin\*\您的项目.vsix
将 .vsix 文件复制到要安装扩展的目标计算机上。 该计算机可为您自己的计算机或其他计算机。
- 目标计算机必须装有 source.extension.vsixmanifest 中指定的 Visual Studio 版本之一。
在目标计算机上,打开 .vsix 文件。
**“Visual Studio Extension Installer”**将会打开并安装扩展。
启动或重新启动 Visual Studio。
卸载扩展
在 *** 工具 *** 菜单中,选择 *** 扩展管理器 ***。
展开**“已安装的扩展”**。
选择扩展,然后选择 *** 卸载 ***。
在极少数情况下,有错误的扩展无法加载并在错误窗口中创建报告,但不显示在扩展管理器中。 在此情况下,可以通过从以下位置删除文件来删除扩展,其中 %LocalAppData% 通常为 DriveName:\Users\UserName\AppData\Local:
%LocalAppData%\Microsoft\VisualStudio\11.0\Extensions
示例
此示例在元素间的依赖关系中查找循环。
它将对保存命令和验证菜单命令都进行验证。
/// <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;
}