在处理包含大量项目的Visual Studio解决方案时,经常需要理解项目之间的依赖关系。虽然市面上存在一些工具和应用程序可以展示这些依赖关系,但它们要么只支持旧版本的Visual Studio,要么生成的图表杂乱无章,难以阅读。因此,决定创建一个基于文本的、树状视图的依赖关系浏览器,它简单直观,能够展示项目的依赖关系,无论是以层次结构还是扁平列表的形式。
这个工具提供了以下功能:
尽管这个工具很有用,但它也有一些局限性:
层次结构视图允许用户逐层查看每个项目的依赖关系。这种视图对于理解项目之间的复杂关系非常有用。
在扁平列表视图中,应用程序会自动展开项目的依赖关系,并以列表形式展示所有唯一的依赖项。这种视图有助于快速识别项目依赖的全局视图。
用户可以选择一个项目,并查看哪些项目引用了所选项目。这有助于理解项目在解决方案中的作用和重要性。
这个工具的代码非常简单,几乎没有错误检查,基本上是直接实现。值得注意的是,解决方案文件不是XML文档,而项目文件(csproj)是。这让人不禁好奇。
读取解决方案文件涉及大量的字符串检查和处理,创建一个包含项目名称和项目路径的字典。
public class Solution {
protected Dictionary<string, string> projectPaths;
public Dictionary<string, string> ProjectPaths {
get {
return projectPaths;
}
}
public Solution() {
projectPaths = new Dictionary<string, string>();
}
public void Read(string filename) {
StreamReader sr = new StreamReader(filename);
string solPath = Path.GetDirectoryName(filename);
while (!sr.EndOfStream) {
string line = sr.ReadLine();
if (!String.IsNullOrEmpty(line)) {
if (line.StartsWith("Project")) {
string projName = StringHelpers.Between(line, '=', ',');
projName = projName.Replace("\"", "").Trim();
string projPath = StringHelpers.RightOf(line, ',');
projPath = StringHelpers.Between(projPath, "\"", "\"");
projPath = projPath.Replace("\"", "").Trim();
if (projPath.EndsWith(".csproj")) {
projPath = Path.Combine(solPath, projPath);
projectPaths.Add(projName, projPath);
}
}
}
}
sr.Close();
}
}
与解决方案文件不同,项目文件是一个XML文件。在处理时,需要指定XML命名空间。
public class Project {
protected Dictionary<string, string> referencedProjects;
protected List<Project> dependencies;
public string Name { get; set; }
public Dictionary<string, string> ReferencedProjects {
get {
return referencedProjects;
}
}
public List<Project> Dependencies {
get {
return dependencies;
}
}
public Project() {
referencedProjects = new Dictionary<string, string>();
dependencies = new List<Project>();
}
public void Read(string filename) {
XDocument xdoc = XDocument.Load(filename);
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
foreach (var projRef in from el in xdoc.Root.Elements(ns + "ItemGroup").Elements(ns + "ProjectReference")
select new {
Path = el.Attribute("Include").Value,
Name = el.Element(ns + "Name").Value
}) {
string projPath = Path.GetDirectoryName(filename);
projPath = Path.Combine(projPath, projRef.Path);
referencedProjects.Add(projRef.Name, projPath);
}
}
}
使用一个单独的方法将解决方案和项目结合起来,创建另一个字典,这次键是项目名称,值是Project实例。这个方法还填充了Project类的Dependencies集合。
protected Dictionary<string, Project> projects;
protected void ParseSolution(string filename) {
projects = new Dictionary<string, Project>();
Solution sol = new Solution();
sol.Read(filename);
foreach (KeyValuePair<string, string> kvp in sol.ProjectPaths) {
Project proj = new Project();
proj.Name = kvp.Key;
proj.Read(kvp.Value);
projects.Add(proj.Name, proj);
}
foreach (KeyValuePair<string, Project> kvp in projects) {
foreach (string refProjName in kvp.Value.ReferencedProjects.Keys) {
Project refProject = projects[refProjName];
kvp.Value.Dependencies.Add(refProject);
}
}
}
填充依赖关系图有两种方式:层次结构或扁平化。两种方式的代码相似,主要区别在于是否创建子节点。
protected void PopulateNewLevel(TreeNode node, ICollection<Project> projects) {
List<string> nodeNames = new List<string>();
foreach (Project p in projects) {
TreeNode tn = new TreeNode(p.Name);
node.Nodes.Add(tn);
if (asTree) {
PopulateNewLevel(tn, p.Dependencies);
} else {
PopulateSameLevel(tn, p.Dependencies);
}
}
}
protected void PopulateSameLevel(TreeNode node, ICollection<Project> projects) {
foreach (Project p in projects) {
bool found = false;
foreach (TreeNode child in node.Nodes) {
if (child.Text == p.Name) {
found = true;
break;
}
}
if (!found) {
TreeNode tn = new TreeNode(p.Name);
node.Nodes.Add(tn);
}
PopulateSameLevel(node, p.Dependencies);
}
}
protected void PopulateDependencyOfProjects(TreeNode node, ICollection<Project> projects) {
foreach (Project p in projects) {
TreeNode tn = new TreeNode(p.Name);
node.Nodes.Add(tn);
foreach (Project pdep in projects) {
foreach (Project dep in pdep.Dependencies) {
if (p.Name == dep.Name) {
bool found = false;
foreach (TreeNode tnDep in tn.Nodes) {
if (tnDep.Text == pdep.Name) {
found = true;
break;
}
}
if (!found) {
TreeNode tn2 = new TreeNode(pdep.Name);
tn.Nodes.Add(tn2);
}
}
}
}
}
}