Reducing Dependencies In .NET Development
by Scott Allen


Listing One
[Plugin("SLN", Description = "Visual Studio.NET Solution")]
public class SLNAnalyzer : DependencyAnalyzer
{
    /// <summary>
    /// Pull dependencies from a SLN file. 
    /// SLN files look like this (some lines snipped for space)
    /// Project("{SGUID}") = "PNAME1", "PNAME1.csproj", "{PGUID1}"
    /// EndProject
    /// Project("{SGUID}") = "PNAME2", "PNAME2.csproj", "{PGUID2}"
    /// EndProject
    /// Global
    ///      GlobalSection(ProjectDependencies) = postSolution
    ///         {PGUID1}.0 = {DGUID1}
    ///         {PGUID1}.1 = {DGUID2}
    ///         {PGUID2}.0 = {DGUID1}
    ///      EndGlobalSection
    /// EndGlobal
    /// </summary>
    /// <param name="textReader">The stream to read from</param>
    public override void Parse(TextReader textReader)
    {       
        VerifyFile(textReader);
        dependencies = new Hashtable();
        ParseProjects(textReader);
        ParseDependencies(textReader);
    }
    /// <summary>
    /// Check line for a specifc header, should be first line of the file
    /// </summary>
    /// <param name="textReader">The stream to read from</param>
    private void VerifyFile(TextReader textReader)
    {
        string header = textReader.ReadLine();
        if(header.CompareTo(FILE_HEADER) != 0)
        {
            throw new ArgumentException(
                            "File is not in expected format");
        }
    }
    /// <summary>
    /// All of the projects in the SLN are listed first. As shown 
    /// above, each project has a GUID.
    /// </summary>
    /// <param name="textReader">The stream to read from</param>
    private void ParseProjects(TextReader textReader)
    {
        string line = textReader.ReadLine();
        while(line != null && 
            line.IndexOf(DEPENDENCY_START) < 0)
        {
            if(line.IndexOf(PROJECT_ENTRY_START) == 0)
            {
                // split the line apart by spaces. this places the project 
                // name in [2] and the giud in [4], must trim off uneeded 
                // characters from beginning and end
                string [] parts = line.Split(' ');  
                string name = parts[2].Trim(SLNELEMENT_TRIM);
                string guid = parts[4].Trim(SLNELEMENT_TRIM); 
                dependencies.Add(guid, new DependencyNode(name));
            }
            line = textReader.ReadLine();
        }           
    }
    /// <summary>
    /// Dependencies of all projects are listed in a distinct section of file.
    /// </summary>
    /// <param name="textReader">The stream to read from</param>
    private void ParseDependencies(TextReader textReader)
    {
        string line = textReader.ReadLine();
        while(line != null &&
            line.IndexOf(DEPENDENCY_END) < 0)
        {
            string [] parts = line.Split(' ');
            // for the parent guid, we need strip the leading 
            // tab characters, and the trailing .N
            string childGuid = parts[2];
            string parentGuid = parts[0].Trim(); 
            parentGuid = parentGuid.Substring(0, 
                                    parentGuid.LastIndexOf("."));
            DependencyNode parentNode;
            DependencyNode childNode;
            parentNode = (DependencyNode)dependencies[parentGuid];
            childNode = (DependencyNode)dependencies[childGuid];

            parentNode.Dependencies.Add(childNode);
            line = textReader.ReadLine();
        }
    }
    private const string FILE_HEADER = "Microsoft Visual Studio "
                           + "Solution File, Format Version 7.00";
    private const string DEPENDENCY_START = "GlobalSection"
                           + "(ProjectDependencies)";
    private const string DEPENDENCY_END = "EndGlobalSection";
    private const string PROJECT_ENTRY_START = "Project(\"{";
    private readonly char[] SLNELEMENT_TRIM = {'\"', ','};
}

Listing Two
/// <summary>
/// A DependencyNode keeps state for a dependency: name, 
/// visibility, visio shape, and child dependencies
/// </summary>
public class DependencyNode
{
    public DependencyNode(string name)
    {
        if(name == null)
        {
            throw new ArgumentNullException();
        }
        this.name = name;
        visible = true;
        dependencies = new ArrayList();
    }
    public string Name
    {
        get { return name;  }
        set { name = value; }
    }
    public bool Visible
    {
        get { return visible; }
        set { visible = value; }
    }
    public Visio.IVShape Shape
    {
        get { return shape; }
        set { shape = value; }
    }
    public ArrayList Dependencies
    {
        get { return dependencies; }
    }
    protected string name;
    protected bool visible;
    protected Visio.IVShape shape;
    protected ArrayList dependencies;
};


Listing Three
/// <summary>
/// This custom attribute is applied to classes implementing 
//  DependencyAnalyzer. It allows class to announce supported file extension.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class PluginAttribute : System.Attribute
{
    public PluginAttribute(string fileExtension)
    {
        this.fileExtension = fileExtension;
    }
    public string FileExtension
    {
        get { return fileExtension; }
    }
    public string Description
    {
        get { return description; }
        set { description = value; }
    }
    private string fileExtension;
    private string description;
};

Listing Four
<?xml version="1.0" encoding="utf-8" ?>
<Plugins xmlns="http://tempuri.org/Plugins.xsd">
	<Assembly Name="DSWAnalyzer.dll" />
	<Assembly Name="SLNAnalyzer.dll" />
</Plugins>






3


