In my last postI talked about exporting solution information from Visual Studio using a macro to create an xml file. In this post I’m going to use this exported information to answer questions about the health of my solution and highlight things that I may wish to investigate further to improve consistency and hopefully get rid of any nasty surprises. The following methods query the xml output that has been loaded into an XDocument and dump the results to another XDocument.
Find all projects with names that don’t match the output file name – there may well be a legitimate reason for naming an output file differently from a project, but if there isn’t, for example when a project has been renamed and the output file name hasn’t, it can lead to confusion.
///
/// Find all of the projects that have a output file name that doesn't match the project name
///
private void ProjectsWithNamesNotMatchingOutputFileName(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where c.Descendants("property")
.Where(p => p.Attribute("type")
.Value == "OutputFileName").Count() > 0
&& c.Attribute("name").Value !=
Path.GetFileNameWithoutExtension(
c.Descendants("property")
.Where(p => p.Attribute("type").Value == "OutputFileName")
.ElementAt(0).Value)
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsWithNamesNotMatchingOutputFileName"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value),
new XAttribute("value",
x.Descendants("property")
.Where(p => p.Attribute("type")
.Value == "OutputFileName")
.ElementAt(0).Value)));
}
root.Add(resultSet);
}
Find all projects with names that don’t match the containing folder name – again there may be a legitimate reasons for doing this but 9 time out of 10 this will occur when someone has renamed a project and not updated the file system to reflect the change. This can lead to confusion when viewing the solution in explorer.
///
/// Find all of the projects with names that don't match the containing folder name
///
private void ProjectsWithNamesNotMatchingContainingFolder(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where !c.Attribute("fullname").Value.EndsWith("\\") &&
Path.GetDirectoryName(c.Attribute("fullname").Value)
.TrimEnd('\\')
.Substring(Path.GetDirectoryName(
c.Attribute("fullname").Value)
.LastIndexOf("\\") + 1) !=
c.Attribute("name").Value
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsWithNamesNotMatchingContainingFolder"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value),
new XAttribute("value",
Path.GetDirectoryName(x.Attribute("fullname").Value)
.TrimEnd('\\')
.Substring(Path.GetDirectoryName(
x.Attribute("fullname").Value)
.LastIndexOf("\\") + 1))));
}
root.Add(resultSet);
}
Find all projects with less than 5 classes – most of the time projects with few classes in them cause more harm than good especially if there are lots of them in a solution. This adds cost at development and compile time, cost at deployment time and cost at runtime. See here http://www.theserverside.net/tt/articles/showarticle.tss?id=ControllingDependencies for a detailed explanation of the problems.
///
/// Find all projects that have less than 5 classes
///
private void ProjectsWithLessThan5Classes(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where c.Descendants("codeItem")
.Where(i => i.Attribute("type").Value == "class")
.Count() < 5
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsWithLessThan5Classes"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value),
new XAttribute("value", x.Descendants("codeItem")
.Where(i => i.Attribute("type").Value == "class")
.Count())));
}
root.Add(resultSet);
}
Find all projects with less than 5 code artifacts – this is closely related to the previous example but it is expanded to include all .Net types such as structs, interfaces and enums.
///
/// Find all projects that have less than 5 classes
///
private void ProjectsWithLessThan5CodeArtifacts(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where c.Descendants("codeItem").Count() < 5
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsWithLessThan5CodeArtifacts"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value),
new XAttribute("value", x.Descendants("codeItem")
.Count())));
}
root.Add(resultSet);
}
Find duplicate output file names – this may occur when 2 or more projects may or may not have the same name but are set to produce output files with the same name. This is a bad idea that can lead to confusion.
///
/// Find all of the projects that have duplicated output file names
///
private void ProjectsDuplicateOutputFileName(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
.GroupBy(p => p.Descendants("property")
.Where(a => a.Attribute("type").Value == "OutputFileName"))
.Where(g => g.Count() > 1)
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsDuplicateOutputFileName"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value),
new XAttribute("value", x.Descendants("property")
.Where(p => p.Attribute("type")
.Value == "OutputFileName")
.ElementAt(0).Value)));
}
root.Add(resultSet);
}
Find all the projects that don’t a file called VersionInfo.cs in them – this is used to make sure all of the dlls in the solution contain version information.
///
/// All of the projects which don't have a version.cs file in the root
///
///
private void ProjectsMissingVersionCS(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where c.Descendants("codeFile")
.Where(a => a.Attribute("name").Value.ToLower() == "versioninfo.cs")
.Count() == 0
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsMissingVersionCS"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value)));
}
root.Add(resultSet);
}
List all of the code artifacts that are not in the root namespace for the project – there may well be legitimate reasons why code in a project is not in the default namespace for the project. However most often this is caused by people being lazy and not making sure when they move code between projects they update the namespaces. Keeping consistency with things like this makes the code much easier to understand for new people looking at it.
///
/// List all code assets that are not in the default namsepace defined for containing project
///
private void CodeAssetsNotInTheDefaultNamespace(XDocument loaded)
{
var q = from c in loaded.Descendants("codeItem")
where !c.Attribute("fullname").Value
.StartsWith(
c.Ancestors("project")
.Descendants("property")
.Where(o => o.Attribute("type").Value == "RootNamespace")
.ElementAt(0).Value)
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"CodeAssetsNotInTheDefaultNamespace"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("type").Value),
new XAttribute("expectedValue", x.Ancestors("project")
.Descendants("property")
.Where(o =>
o.Attribute("type").Value == "RootNamespace")
.ElementAt(0).Value),
new XAttribute("value", x.Attribute("fullname").Value)));
}
root.Add(resultSet);
}
Find projects that are not reference by other projects in the solution – they are certainly genuine reasons why this can happen but it can also happen when old code is left lying around and it not cleaned up properly. Again this makes life much harder for new people looking at the code. How are they supposed to know what isn’t being used anymore?
///
/// Find all of the projects that are not referenced by other projects in the solution
///
private void ProjectsNotReferencedInTheSolution(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where (from d in loaded.Descendants("reference")
where d.Descendants("path")
.Where(p =>
c.Descendants("property")
.Where(o => o.Attribute("type").Value == "OutputFileName")
.Count() > 0 &&
p.Value == Path.Combine(
Path.Combine(
Path.GetDirectoryName(
c.Attribute("fullname").Value)
, "bin\\Debug"
),
c.Descendants("property")
.Where(o =>
o.Attribute("type").Value == "OutputFileName")
.ElementAt(0).Value
)
)
.Count() == 0
select d).Count() == 0
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsNotReferencedInTheSolution"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value)));
}
root.Add(resultSet);
}
Find all projects that are only referenced by one other project in the solution – there could be genuine reasons for this but it is a good indicator that the projects should be merged to avoid the dependency problems that I touched on earlier, especially if the project has very few types in it.
///
/// Find all of the projects with names that don't match the containing folder name
///
private void ProjectsReferencedOnceInTheSolution(XDocument loaded)
{
var q = from c in loaded.Descendants("project")
where (from d in loaded.Descendants("reference")
where d.Descendants("path")
.Where(p =>
c.Descendants("property")
.Where(o => o.Attribute("type").Value == "OutputFileName")
.Count() > 0 &&
p.Value == Path.Combine(
Path.Combine(
Path.GetDirectoryName(
c.Attribute("fullname").Value),
"bin\\Debug"
),
c.Descendants("property")
.Where(o => o.Attribute("type").Value == "OutputFileName")
.ElementAt(0).Value
)
)
.Count() == 1
select d).Count() == 0
select c;
XElement resultSet = new XElement("resultSet",
new XAttribute("type",
"ProjectsReferencedOnceInTheSolution"));
foreach (XElement x in q)
{
resultSet.Add(new XElement("result",
new XAttribute("name", x.Attribute("name").Value)));
}
root.Add(resultSet);
}
You can see from these examples that we can answer quite a few questions about the health of a solution by using an xml file exported using a macro. Obviously it isn’t the slickest way to do this like you could achieve by writing an AddIn with a UI but it can be very useful when trying to make sense of a large complicated and potentially very messy inherited solution.