Adding Custom Architecture Validation to Layer Diagrams
In Visual Studio 2010 Ultimate and Visual Studio 2010 Premium, users can validate the source code in a Visual Studio project against a layer model so that they can verify that the source code conforms to the dependencies on a layer diagram. There is a standard validation algorithm, but this Visual Studio 2010 feature pack lets you define your own validation extensions for Visual Studio Ultimate and Visual Studio Premium. For more information, see Visual Studio Feature Packs.
When the user selects the Validate Architecture command on a layer diagram, the standard validation method is invoked, followed by any validation extensions that have been installed.
Note
Validation in a layer diagram is not the same as validation in UML diagrams. In a layer diagram, the main purpose is to compare the diagram with the program code in other parts of the solution.
You can package your layer validation extension into a Visual Studio Integration Extension (VSIX), which you can distribute to other Visual Studio Ultimate users. You can either place your validator in a VSIX by itself, or you can combine it in the same VSIX as other extensions. You should write the code of the validator in its own Visual Studio project, not in the same project as other extensions.
Requirements
For requirements and installation instructions, see Requirements in Creating Extensions for Layer Diagrams.
Defining a Layer Validator in a New VSIX
The quickest method of creating a validator is to use the project template. This places the code and the VSIX manifest into the same project.
To define an extension by using a project template
Create a project in a new solution, by using the New Project command on the File menu.
In the New Project dialog box, under Modeling Projects, select Layer Designer Validation Extension.
The template creates a project that contains a small example.
Edit the code to define your validation. For more information, see Programming Validation.
To test the extension, see Debugging Layer Validation.
Note
Your method will be called only in specific circumstances, and breakpoints will not work automatically. For more information, see Debugging Layer Validation.
To install the extension in the main instance of Visual Studio, or on another computer, find the .vsix file in bin\*. Copy it to the computer where you want to install it, and then double-click it. To uninstall it, use Extension Manager on the Tools menu.
Adding a Layer Validator to a Separate VSIX
If you want to create one VSIX that contains layer validators, commands, and other extensions, we recommend that you create one project to define the VSIX, and separate projects for the handlers. For information about other types of modeling extension, see Extending UML Models and Diagrams.
To add layer validation to a separate VSIX
Create a Class Library project in a new or existing Visual Studio Ultimate solution. In the New Project dialog box, click Visual C# and then click Class Library. This project will contain the layer validation class.
Identify or create a VSIX project in your solution. A VSIX project contains a file that is named source.extension.vsixmanifest. If you have to add a VSIX project, follow these steps:
In the New Project dialog box, expand Visual C#, then click Extensibility, and then click VSIX Project.
In Solution Explorer, right-click the VSIX project and then click Set as Startup Project.
Click Select Editions and make sure that Visual Studio Ultimate is checked.
In source.extension.vsixmanifest, under Content, add the layer validation project as a MEF component:
Click Add Content.
At Select a content type, select MEF Component.
At Select a source, click Project and select the name of your command or gesture handler project.
Save the file.
Add the layer validation project as a Custom Extension:
Click Add Content.
At Select a content type, select Custom Extension Type
At Type, enter Microsoft.VisualStudio.ArchitectureTools.Layer.Validator
At Select a source, click Project and select the name of your validation class library project.
Under References click Add Reference and select the runtime for this feature pack.
Return to the layer validation project, and add the following project references:
Reference
What this allows you to do
If you have Visual Studio 2010 Visualization and Modeling Feature Pack installed:
%LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.GraphModel.dll
If you have Visual Studio 2010 Feature Pack 2 installed:
…\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.GraphModel.dll
Read the architecture graph
If you have Visual Studio 2010 Visualization and Modeling Feature Pack installed:
%LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema.dll
If you have Visual Studio 2010 Feature Pack 2 installed:
…\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\ Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.CodeSchema.dll
Read the code DOM associated with layers
If you have Visual Studio 2010 Visualization and Modeling Feature Pack installed:
%LocalAppData%\Microsoft\VisualStudio\10.0\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer.dll
If you have Visual Studio 2010 Feature Pack 2 installed:
…\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Visualization and Modeling Feature Pack Runtime\1.0\Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer.dll
Read the Layer model
Microsoft.VisualStudio.Uml.Interfaces
Read the Layer model
Microsoft.VisualStudio.ArchitectureTools.Extensibility
Read and update shapes and diagrams.
System.ComponentModel.Composition
Define the validation component using Managed Extensibility Framework (MEF)
Microsoft.VisualStudio.Modeling.Sdk.10.0
Define modeling extensions
Note
%LocalAppData% is typically DriveName:\Users\UserName\AppData\Local. On Windows XP or Windows 2003, use %AppData% instead of %LocalAppData%.
Edit the class file in the C# class library project to contain the code for your validation. For more information, see Programming Validation.
To test the extension, see Debugging Layer Validation.
Note
Your method will be called only in specific circumstances, and breakpoints will not work automatically. For more information, see Debugging Layer Validation.
To install the VSIX in the main instance of Visual Studio, or on another computer, find the .vsix file in the bin directory of the VSIX project. Copy it to the computer where you want to install the VSIX. Double-click the VSIX file in Windows Explorer.
To uninstall it, use Extension Manager on the Tools menu.
Programming Validation
To define a layer validation extension, you define a class that has the following characteristics:
The overall form of the declaration is as follows:
[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); }
When you discover an error, you can report it by using LogValidationError().
When the user invokes the Validate Architecture menu command, the layer runtime system analyses the layers and their artifacts to produce a graph. The graph has four parts:
The layer models of the Visual Studio solution that are represented as nodes and links in the graph.
The code, project items, and other artifacts that are defined in the solution and represented as nodes, and links that represent the dependencies discovered by the analysis process.
Links from the layer nodes to the code artifact nodes.
Nodes that represent errors discovered by the validator.
When the graph has been constructed, the standard validation method is called. When this is complete, any installed extension validation methods are called in unspecified order. The graph is passed to each ValidateArchitecture method, which can scan the graph and report any errors that it finds.
Note
This is not the same as the validation process that is applied to UML diagrams, and it is not the same as the validation process that can be used in domain-specific languages.
Validation methods should not change the layer model or the code that is being validated.
The graph model is defined in Microsoft.VisualStudio.GraphModel. Its principal classes are Node and Link.
Each Node and each Link has one or more Categories which specify the type of element or relationship that it represents. The nodes of a typical graph have the following categories:
Dsl.LayerModel
Dsl.Layer
Dsl.Reference
CodeSchema_Type
CodeSchema_Namespace
CodeSchema_Type
CodeSchema_Method
CodeSchema_Field
CodeSchema_Property
Links from layers to elements in the code have the category "Represents".
The following code is a typical example of an architecture validation extension. It verifies that every type declared in the code of the solution is referenced at least once by the layer model. The user can control whether this validation should be performed on each layer model by setting a Boolean custom property of the model.
If an error is found, LogValidationError is called.
Debugging Validation
To debug your layer validation extension, press CTRL+F5. An experimental instance of Visual Studio opens. In this instance, open or create a layer model. This model must be associated with code, and must have at least one dependency.
Test with a Solution that contains Dependencies
Validation is not executed unless the following characteristics are present:
There is at least one dependency link on the layer diagram.
There are layers in the model that are associated with code elements.
The first time that you start an experimental instance of Visual Studio to test your validation extension, open or create a solution that has these characteristics.
Run Clean Solution before Validate Architecture
Whenever you update your validation code, use the Clean Solution command on the Build menu in the experimental solution, before you test the Validate command. This is necessary because the results of validation are cached. If you have not updated the test layer diagram or its code, the validation methods will not be executed.
Launch the Debugger Explicitly
Validation runs in a separate process. Therefore, that breakpoints in your validation method will not be triggered. You must attach the debugger to the process explicitly when validation has started.
To attach the debugger to the validation process, insert a call to System.Diagnostics.Debugger.Launch() at the start of your validation method. When the debugging dialog box appears, select the main instance of Visual Studio.
Alternatively, you can insert a call to System.Windows.Forms.MessageBox.Show(). When the message box appears, go to the main instance of Visual Studio and on the Debug menu click Attach to Process. Select the process that is named Graphcmd.exe.
Always start the experimental instance by pressing CTRL+F5 (Start without Debugging).
Deploying a Validation Extension
To install your validation extension on a computer on which Visual Studio Ultimate or Visual Studio Premium is installed, open the VSIX file on the target computer. To install on a computer on which Team Foundation Build is installed, you must manually extract the VSIX contents into an Extensions folder. For more information, see Deploying a Layer Modeling Extension.
Example
The following example illustrates a layer validation extension.
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;
}
}