向层关系图添加自定义体系结构验证

更新:2010 年 12 月

在 Visual Studio 2010 旗舰版和 Visual Studio 2010 高级专业版中,用户可以参照层模型验证 Visual Studio 项目中的源代码,以便可以验证源代码是否符合层关系图上的依赖项。 有一种标准验证算法,但利用此 Visual Studio 2010 功能包,您可以对 Visual Studio 旗舰版和 Visual Studio 高级专业版定义自己的验证扩展。 有关更多信息,请参见 Visual Studio 功能包

当用户在层关系图上选择**“验证体系结构”**命令时,将调用标准验证方法,并后跟已安装的任何验证扩展。

提示

层关系图中的验证与 UML 关系图中的验证不相同。 在层关系图中,主要目的是将关系图与解决方案其他部分中的程序代码进行比较。

您可以将层验证扩展打包到一个 Visual Studio 集成扩展 (VSIX) 中,并可以将其分发给其他 Visual Studio 旗舰版用户。 您可以将验证程序单独放入一个 VSIX 中,也可以将其合并到其他扩展所在的同一个 VSIX 中。 您应在验证程序自己的 Visual Studio 项目中编写验证程序代码,而不是在其他扩展所在的同一项目中编写验证程序代码。

要求

有关要求和安装说明,请参见创建层关系图的扩展 中的要求

在新的 VSIX 中定义层验证程序

创建验证程序的最快速方法是使用项目模板。 这会将代码和 VSIX 清单放入同一个项目中。

使用项目模板定义扩展

  1. 通过使用**“文件”菜单上的“新建项目”**命令,在新解决方案中创建项目。

  2. 在**“新建项目”对话框中的“建模项目”下,选择“层设计器验证扩展”[Layer Designer Validation Extension]**。

    此模板将创建一个包含小示例的项目。

  3. 编辑代码以定义验证。 有关更多信息,请参见编程验证。

  4. 若要测试扩展,请参见调试层验证。

    提示

    仅在特定情形下才调用您的方法,而且断点不会自动工作。 有关更多信息,请参见调试层验证。

  5. 若要在 Visual Studio 的主实例中或在其他计算机上安装扩展,请在 bin\* 中找到 .vsix 文件。 将该文件复制到要安装它的计算机中,然后双击它。 若要卸载该文件,请使用**“工具”菜单上的“扩展管理器”**。

将层验证程序添加到单独的 VSIX 中

若要创建一个包含层验证程序、命令和其他扩展的 VSIX,建议您创建一个项目来定义该 VSIX,并为处理程序创建单独的项目。 有关其他类型的建模扩展的信息,请参见扩展 UML 模型和关系图

将层验证添加到单独的 VSIX 中

  1. 在新的或现有的 Visual Studio 旗舰版解决方案中创建类库项目。 在**“新建项目”对话框中,单击“Visual C#”,再单击“类库”**。 此项目将包含层验证类。

  2. 在您的解决方案中标识或创建 VSIX 项目。 VSIX 项目包含一个名为 source.extension.vsixmanifest 的文件。 如果必须添加一个 VSIX 项目,请按照以下步骤操作:

    1. 在**“新建项目”对话框中,展开“Visual C#”,单击“扩展性”,再单击“VSIX 项目”**。

    2. 在解决方案资源管理器中,右击该 VSIX 项目,然后单击**“设为启动项目”**。

    3. 单击**“选择版本”[Select Editions]**,并确保选中 Visual Studio 旗舰版。

  3. source.extension.vsixmanifest 中的**“内容”**下,将层验证项目添加为 MEF 组件:

    1. 单击**“添加内容”**。

    2. 在**“选择内容类型”处,选择“MEF 组件”**。

    3. 在**“选择源”[Select a source]处,单击“项目”**,并选择命令或笔势处理程序项目的名称。

    4. 保存该文件。

  4. 将层验证项目添加为自定义扩展:

    1. 单击**“添加内容”**。

    2. 在**“选择内容类型”处,选择“自定义扩展类型”**。

    3. 在**“类型”**处,输入 Microsoft.VisualStudio.ArchitectureTools.Layer.Validator。

    4. 在**“选择源”处,单击“项目”**,并选择验证类库项目的名称。

  5. 在**“引用”下,单击“添加引用”**,并选择此功能包的运行时。

  6. 返回到层验证项目,并添加以下项目引用:

    引用

    可完成的操作

    如果您安装了 Visual Studio 2010 可视化和建模功能包:

    %LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.GraphModel.dll

    如果您安装了 Visual Studio 2010 功能包 2:

    …\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.GraphModel.dll

    读取体系结构关系图

    如果您安装了 Visual Studio 2010 可视化和建模功能包:

    %LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema.dll

    如果您安装了 Visual Studio 2010 功能包 2:

    …\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\ Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema.dll

    读取与层关联的代码 DOM

    如果您安装了 Visual Studio 2010 可视化和建模功能包:

    %LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer.dll

    如果您安装了 Visual Studio 2010 功能包 2:

    …\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer.dll

    读取层模型

    Microsoft.VisualStudio.Uml.Interfaces

    读取层模型

    Microsoft.VisualStudio.ArchitectureTools.Extensibility

    读取和更新形状和关系图。

    System.ComponentModel.Composition

    使用 Managed Extensibility Framework (MEF) 定义验证组件

    Microsoft.VisualStudio.Modeling.Sdk.10.0

    定义建模扩展

    提示

    %LocalAppData% 通常为“驱动器名称:\Users\用户名\AppData\Local”。 在 Windows XP 或 Windows 2003 上,使用 %AppData% 而不使用 %LocalAppData%。

  7. 编辑 C# 类库项目中的类文件以包含验证的代码。 有关更多信息,请参见编程验证。

  8. 若要测试扩展,请参见调试层验证。

    提示

    仅在特定情形下才调用您的方法,而且断点不会自动工作。 有关更多信息,请参见调试层验证。

  9. 若要在 Visual Studio 的主实例中或其他计算机上安装 VSIX,请在 VSIX 项目的 bin 目录中找到 .vsix 文件。 将该文件复制到要安装 VSIX 的计算机中。 在 Windows 资源管理器中双击 VSIX 文件。

    若要卸载该文件,请使用**“工具”菜单上的“扩展管理器”**。

编程验证

若要定义层验证扩展,请定义一个具有以下特征的类:

  • 声明的总体形式如下所示:

      [Export(typeof(IValidateArchitectureExtension))]
      public partial class Validator1Extension :
                      IValidateArchitectureExtension
      {
        public void ValidateArchitecture(Graph graph)
        {
          var typeCategory = graph.DocumentSchema
                     .Categories.Get("CodeSchema_Type");
          var allTypes = graph.Nodes.GetByCategory(typeCategory);
        ...
          this.LogValidationError(graph, "SampleErrorId", 
             "Sample Validation Error", GraphErrorLevel.Error, allTypes);
        }
    
  • 当您发现错误时,可使用 LogValidationError() 报告错误。

当用户调用**“验证体系结构”**菜单命令时,层运行时系统对层及其项目进行分析来生成关系图。 此关系图包括四部分:

  • Visual Studio 解决方案的层模型,表示为关系图中的节点和链接。

  • 解决方案中定义的代码、项目项和其他项目,表示为表示分析过程发现的依赖项的节点和链接。

  • 从层节点到代码项目节点的链接。

  • 表示验证程序发现的错误的节点。

关系图构造完成后,调用标准验证方法。 此过程完成后,将按未指定的顺序调用任意已安装的扩展验证方法。 将关系图传递给每个 ValidateArchitecture 方法,然后该方法可以扫描关系图并报告它发现的任何错误。

提示

这与应用到 UML 关系图的验证过程不相同,而且与可通过域特定语言使用的验证过程也不相同。

验证方法不应更改正在验证的层模型或代码。

关系图模型在 Microsoft.VisualStudio.GraphModel 中定义, 其主体类为 Node 和 Link。

每个节点和每个链接都有一个或多个类别,用来指定它表示的元素或关系的类型。 典型关系图的节点具有以下类别:

  • Dsl.LayerModel

  • Dsl.Layer

  • Dsl.Reference

  • CodeSchema_Type

  • CodeSchema_Namespace

  • CodeSchema_Type

  • CodeSchema_Method

  • CodeSchema_Field

  • CodeSchema_Property

代码中从层到元素的链接具有“Represents”类别。

下面的代码是体系结构验证扩展的典型示例。 它验证层模型是否至少引用一次在解决方案代码中声明的每个类型。 用户可以通过设置模型的 Boolean 自定义属性,控制是否应对每个层模型执行此验证。

如果发现错误,则调用 LogValidationError。

调试验证

若要调试层验证扩展,请按 Ctrl+F5。 Visual Studio 的实验实例将打开。 在此实例中打开或创建一个层模型。 此模型必须与代码关联,且必须具有至少一个依赖项。

测试包含依赖项的解决方案

除非具有以下特征,否则不执行验证:

  • 在层关系图上存在至少一个依赖项链接。

  • 模型中存在一些与代码元素关联的层。

首次启动 Visual Studio 的实验实例来测试您的验证扩展时,打开或创建一个具有这些特征的解决方案。

运行“验证体系结构”命令之前运行“清理解决方案”命令

每当更新验证代码时,请首先在实验解决方案中使用**“生成”菜单上的“清理解决方案”**命令,然后再测试“验证”命令。 这是必需的,原因是验证结果会被缓存。 如果您尚未更新测试层关系图或其代码,则将不会执行验证方法。

显式启动调试器

验证将在单独的进程中运行。 因此,不会触发验证方法中的断点。 验证启动后,必须显式地将调试器附加到进程。

若要将调试器附加到验证进程,请在验证方法的开头插入对 System.Diagnostics.Debugger.Launch() 的调用。 当调试对话框出现时,选择 Visual Studio 的主实例。

或者,也可以插入对 System.Windows.Forms.MessageBox.Show() 的调用。 当消息框出现时,转到 Visual Studio 的主实例,并在**“调试”菜单上单击“附加到进程”**。选择名为 Graphcmd.exe 的进程。

始终通过按 Ctrl+F5(“开始执行(不调试)”)来启动此实验实例。

部署验证扩展

若要在安装了 Visual Studio 旗舰版或 Visual Studio 高级专业版的计算机上安装您的验证扩展,请在目标计算机上打开 VSIX 文件。 若要在安装了 Team Foundation Build 的计算机上进行安装,必须将 VSIX 内容手动提取到 Extensions 文件夹中。 有关更多信息,请参见部署层建模扩展

示例

下面的示例阐释了层验证扩展。

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema;

using Microsoft.VisualStudio.GraphModel;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer;

namespace MyValidationExtensions
{ // This attribute identifies a layer validator:
  [Export(typeof(IValidateArchitectureExtension))]
  public partial class UnreferencedTypeValidatorExtension 
      : IValidateArchitectureExtension
  {
    private GraphCategory typeCategory = null;
    /// <summary>
    /// Validate the architecture
    /// </summary>
    /// <param name="graph">The graph</param>
    public void ValidateArchitecture(Graph graph)
    {
      // A good place to attach a debugger
      // System.Windows.Forms.MessageBox.Show("Attach - Unreferenced Type Validator");
      // To find the nodes that represent the code and the layers,
      // we need to filter by category.
      // Categories are identified by specific strings:
      if (typeCategory == null)
      {
        typeCategory = 
          graph.DocumentSchema.Categories.Get("CodeSchema_Type");
      }
      var layerModelCategory = 
        graph.DocumentSchema.Categories.Get("Dsl.LayerModel");
      var allLayerModels =
        graph.Nodes.GetByCategory(layerModelCategory);

      foreach (var layerModel in allLayerModels)
      {
        var allTypesMustBeReferencedProperty =
           ExtractProperty(layerModel, 
            AllTypesMustBeReferencedProperty.FullName);
        bool shouldAllTypesBeReferenced = 
          allTypesMustBeReferencedProperty == null 
          ? false 
          : Convert.ToBoolean(allTypesMustBeReferencedProperty);

        if (shouldAllTypesBeReferenced)
        {
          // Find all types referenced by layers:
          var referencedTypes = new HashSet<Node>();
          GetReferencedTypes(referencedTypes, layerModel);
          var allTypes = graph.Nodes.GetByCategory(typeCategory);

          foreach (var type in allTypes)
          {
            if (!referencedTypes.Contains(type))
            {
              // Filter out types that are not part of any 
              // assembly (for example referenced external types).
              if (type.GetContainmentSources(graph)
                .Where(n => n.HasCategory(graph.DocumentSchema
                .Categories.Get("CodeSchema_Assembly"))).Any())
              {
                // type is not referenced in the layer diagram
                this.LogValidationError(graph,
                    string.Format("{0}_UnreferencedTypeError_{1}",
                    GetFullyQualifiedTypeName(type), Guid.NewGuid()),
                    string.Format("AV1002 : Unreferenced type :"
                      + " {0}{2}Layer Diagram: {1}.layerdiagram.",
                        GetFullyQualifiedTypeName(type),
                        layerModel.Label,
                        Environment.NewLine),
                    GraphErrorLevel.Error,
                    new Node[] { type });
              }
            }
          }
        }
      }
    }

    private void GetReferencedTypes(HashSet<Node> referencedTypes, Node node)
    {
      foreach (Node containedNode in node.GetContainmentTargets(node.Owner))
      {
        if (referencedTypes.Contains(containedNode))
        {
          // We've seen this node before
          continue;
        }

        if (containedNode.HasCategory(typeCategory))
        {
          referencedTypes.Add(containedNode);
        }
        else
        {
          GetReferencedTypes(referencedTypes, containedNode);
        }
      }
    }
    public static string GetFullyQualifiedTypeName(Node typeNode)
    {
      try
      {
         string returnValue;
         CodeQualifiedIdentifierBuilder id = 
           new CodeQualifiedIdentifierBuilder(
                   typeNode.Id, typeNode.Owner);
         returnValue = id.GetFullyQualifiedLabel(
              CodeQualifiedIdentifierBuilder.LabelFormat
              .NoAssemblyPrefix);
        return returnValue;
      }
      catch { }
     return typeNode.Label;
  }

  public static object ExtractProperty
             (Node layerModel, string propertyName)
  {
    object propertyValue = null;
    var propertyCategory = 
        layerModel.Owner.DocumentSchema.GetProperty(propertyName);
    if (propertyCategory != null)
    {
      propertyValue = layerModel[propertyCategory];
    }
    return propertyValue;
  }
}

请参见

其他资源

创建层关系图的扩展

修订记录

日期

修订记录

原因

2010 年 12 月

针对 Visual Studio 2010 功能包 2 更新了内容。

信息补充。