# Visualizing Project Dependencies Automatically

By , 4 Sep 2007

## Introduction

Developer 1: Hey I want to make some changes to CommonLibrary1. Is that going to affect the 15 other projects people are working on?

Developer 2: I dunno, ask John.

Developer 1: He told me to ask you.

Sound familiar? I wanted a quick easy, foolproof way to visualize project dependencies without depending on human intervention, i.e. some poor sap typing stuff into a Word document which would probably be wrong before he saved the document.

This project attempts to solve that by taking a directory location and an output JPG location as parameters. It then scans the directories getting all the *.csproj files and determines their relationships. It then uses the graphviz package to visualize these dependencies quickly and easily with fancy schmancy arrows and circles.

## Background

I thought for sure that someone would have done this already but I could only find something similar for C++ apps. Even if there was an existing thing that did this, chances are it wouldn't work for the code structure I like to use. Different companies like to do their references and what not differently, so it would be nice to own the code and modify it according to personal preference... hence this project was born.

I like to put in post build events for code that will copy the output DLLs to a directory called References. That way, whether it is a debug or release compile, the referencing projects all point to the same place and everyone has consistent references. This project assumes that structure so if you have something different then you will need to make code changes. Don't worry, I have included 3 sample projects and full unit tests so you can figure out how it's supposed to work first, then change it to suit your needs. You'll need Nunit to run the tests. It's free, get it.

## Using the Code

In the DependencyTracker/Launch directory, there is a *.bat file which will generate two JPG files: one for the library code projects only and one that generates a JPG for all projects. By changing the search directory and output JPG locations, you can customize it to your scenario and then schedule it to run on whatever schedule works best. Output to a network drive which happens to be on a Web server and you have instant up to date documentation which is easily accessible.

Most of the magic happens in Project.cs when it is trying to find the references:

/// <summary>
/// Load project file from disk and get list of project paths
/// </summary>
/// <returns></returns>
private string[] GetReferenceProjectPaths()
{
XmlDocument project = new XmlDocument();
XmlNamespaceManager namespaceManager =
new XmlNamespaceManager(project.NameTable);
"ns",
"http://schemas.microsoft.com/developer/msbuild/2003");

Nothing earth shattering, just parsing the *.csproj file which is XML and looking for the references. The only complication is that the reference is stored as a relative file path, but by changing the current directory to where the project is and then just browsing to that path, we can get to the referenced DLL pretty easy. Then we just have to figure out where the source project is given the compiled DLL's location. As mentioned previously, I like to keep a consistent "References" folder so it isn't too difficult to find the referenced project file from there. If you have a different structure, you will need to make changes here.

After all of this is loaded in memory, we then call out to a command line program to launch the graphviz image generation based on a file that is output below. We must recursively parse all the various projects and dependencies that have been loaded and take care not to process the same one twice so we don't get phantom arrows on the graph. The basic format of the graphviz program is to take a *.dot file which has lines like "a -> b" which means a points to b.

/// <summary>
/// This will output all dependencies since that is what we care about.
/// Will need some additional work if we want to display
/// straggler projects with no dependencies also.
/// </summary>
private void AppendProjectLinks(StringBuilder sb, ProjectList projects)
{
//loop through every project and output its dependencies to the
//file in the format
//parent -> child
//order doesn't matter for dot files
foreach (Project project in projects)
foreach (Project reference in project.ReferencedProjects)
{
project.Name + "-" + reference.Name)
)
{
sb.Append(
"\"" + project.Name + "\" -> \"" +
reference.Name + "\"" + Environment.NewLine
);
}

}
}

## Points of Interest

All the tests use relative paths so it shouldn't matter where you unzip the files to, they should work as long as you don't move any of the folders around in the zip file.

## History

• 17-August-2007 - Initial version
• 04-September-2007 - Changed dot.exe launch method to use registry key

 Paul B. United States Member
I've been a software developer since 1996 and have enjoyed C# since 2003. I have a Bachelor's degree in Computer Science and for some reason, a Master's degree in Business Administration. I currently do software development contracting/consulting.

Votes of 3 or less require a comment

 Search this forum Profile popups    Spacing RelaxedCompactTight   Noise Very HighHighMediumLowVery Low   Layout Open AllThread ViewNo JavascriptPreview   Per page 102550
 First Prev Next
 Nice Job 3779886 21 May '08 - 6:25
 Fixed infinite loop problem Doug in NH 12 Sep '07 - 2:55
 Re: Fixed infinite loop problem Paul B. 12 Sep '07 - 4:04
 To add support for VB Projects... Doug in NH 12 Sep '07 - 2:47
 To add support for VB.NET projects, modify project.cs. 1. Find this line (21) in the constructor and add the check for .vbproj as shown below. if (projectInfo.Extension != ".csproj" && projectInfo.Extension != ".vbproj") throw new ArgumentException("Extension " + projectInfo.Extension + " is not valid"); 2. Add the following code in the GetReferenceProjectPaths method just above the return statement (around line 90).                referenceNodes = project.SelectNodes("//ns:Project/ns:ItemGroup/ns:ProjectReference/@Include", namespaceManager);               foreach (XmlNode node in referenceNodes)               {                    //start from location of project file since hint path is relative to there                    Directory.SetCurrentDirectory(new FileInfo(ProjectPath).Directory.FullName);                     //assume name of file - extension is name of project for now                    string ProjectName = node.Value;                    FileInfo info = new FileInfo(ProjectName);                    string ProjectFilePath = ""; //init to nothing initially                    string[] ProjectFilePaths = Directory.GetFiles(info.Directory.FullName, info.Name, SearchOption.AllDirectories);                     //if anything found, use it                    //TODO: Should we raise an event here to let people know we couldn't find a project?                    if ((ProjectFilePaths != null && ProjectFilePaths.GetUpperBound(0) >= 0))                         ProjectFilePath = ProjectFilePaths[0]; //get first match                     //if something found add to list                    if (!String.IsNullOrEmpty(ProjectFilePath))                         //get first match on project file                         returnItems.Add(ProjectFilePath);               }  -Doug Sign In·View Thread·Permalink
 Too much hard coded stuff Vagif Abilov 4 Sep '07 - 2:13
 Well, this is not type of code you distribute:   //call out to graphviz to create JPG file System.Diagnostics.Process.Start( @"C:\Program Files\ATT\Graphviz\bin\dot.exe", "-Tjpg \"" + DOTFile + "\" \"-o" + OutputJPGFilePath + "\"" );   Вагиф Абилов MCP (Visual C++) Oslo, Norway   If you're in a war, instead of throwing a hand grenade at the enemy, throw one of those small pumpkins. Maybe it'll make everyone think how stupid war is, and while they are thinking, you can throw a real grenade at them. Jack Handey. Sign In·View Thread·Permalink
 Re: Too much hard coded stuff Paul B. 4 Sep '07 - 3:59
 The example wasn't meant to be a perfect example of how to launch an external program, it was meant to demonstrate how to visualize the projects. There are unit tests and if this particular assumption was flawed, one of the tests would fail, so it would be fairly obvious what the problem was. At the time of writing, the time it would take to figure out where the registry entry was, etc. didn't seem to be worth the time given that it was an internal app run on a schedule automatically.   However, a few people have had problems with that so I went ahead and changed it to lookup the registry entry and updated the zip file. The code you posted in your comment was changed to the below:   //get a reference to the registry key used for graphviz Microsoft.Win32.RegistryKey GraphvizKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software\\ATT\\Graphviz"); if (GraphvizKey == null) throw new ApplicationException("Unable to find Graphviz registry entry - is it installed?");   //get path to dot.exe, should be in bin directory in install path string DotExePath = System.IO.Path.Combine(GraphvizKey.GetValue("InstallPath").ToString(), "bin\\dot.exe");   //call out to graphviz to create JPG file System.Diagnostics.Process.Start( DotExePath, "-Tjpg \"" + DOTFile + "\" \"-o" + OutputJPGFilePath + "\"" ); Sign In·View Thread·Permalink
 Bug on non english OS Lukas Ith 20 Aug '07 - 0:59
 Hi Paul.   I like the idea of this tool   There is a minor bug. When running on a non english os, the folder 'program files' cannot be found.   ...@"C:\Program Files\ATT\Graphviz\bin\dot.exe"...   Use the %programfiles% system variable instead: string path = Path.Combine(Environment.ExpandEnvironmentVariables("%programfiles%"), @"ATT\Graphviz\bin\dot.exe"); System.Diagnostics.Process.Start( path, "-Tjpg \"" + DOTFile + "\" \"-o" + OutputJPGFilePath + "\"" );   Lukas Sign In·View Thread·Permalink
 Re: Bug on non english OS Paul B. 20 Aug '07 - 3:26
 If I was being very robust about it I would get the install path for the program from the registry (because the user could type in a different path during install), but this was just a quick couple hour, internal use kind of thing. I didn't even know non-english OS's had a different program files directory, so thanks for the info. Sign In·View Thread·Permalink
 Last Visit: 31 Dec '99 - 18:00     Last Update: 24 May '13 - 9:02 Refresh 1