Other Posts in Visual Studio

  1. Getting Public Key and Public Key Token of a DLL, the Easy Way
  2. Automatically Splitting a Project into Smaller Projects in Visual Studio
  3. Basic Shortcuts in Visual Studio That I Use

Automatically Splitting a Project into Smaller Projects in Visual Studio

9/17/2010

When I released the last build of Craig's Utility Library, you may have noticed that there was a download that would allow you to get separate DLLs for each namespace. I mean if you're not going to use Utilities.Encryption, then why the heck should it be there in your project? Plus the main DLL was referencing things like System.Speech, System.Web, etc. and if you don't need the parts that needed those references, you're just sort of adding bloat (although the way .Net works, it doesn't really load it until needed, so maybe not that much bloat). Anyway, my solution was to break it down along namespace lines to reduce the overall size for end products. The way I did this wasn't through copy and paste of 20 different sections of code, instead I created a utility to do it for me (note that this was created with Visual Studio 2010 in mind, I have no idea if it will work for earlier versions):

   1: class Program
   2:  
   3: {
   4:  
   5:     static void Main(string[] args)
   6:  
   7:     {
   8:  
   9:         string Text = Utilities.IO.FileManager.GetFileContents(@"Utilities project location");
  10:  
  11:         XmlDocument Doc = new XmlDocument();
  12:  
  13:  
  14:  
  15:         Doc.LoadXml(Text);
  16:  
  17:         XmlNamespaceManager Manager = new XmlNamespaceManager(Doc.NameTable);
  18:  
  19:         Manager.AddNamespace("Util", "http://schemas.microsoft.com/developer/msbuild/2003");
  20:  
  21:         XmlNodeList Nodes = Doc.DocumentElement.SelectNodes("//Util:ItemGroup/Util:Compile", Manager);
  22:  
  23:         List<Project> Projects = new List<Project>();
  24:  
  25:         foreach (XmlNode Node in Nodes)
  26:  
  27:         {
  28:  
  29:             string Path = Node.Attributes["Include"].InnerText;
  30:  
  31:             string[] Splitter = new string[] { "\\" };
  32:  
  33:             string[] PathItems = Path.Split(Splitter, StringSplitOptions.None);
  34:  
  35:             if (Projects.Find(x => x.Name == PathItems[0]) != null)
  36:  
  37:             {
  38:  
  39:                 Projects.Find(x => x.Name == PathItems[0]).Includes.Add(Path);
  40:  
  41:             }
  42:  
  43:             else if (PathItems[0] != "Properties")
  44:  
  45:             {
  46:  
  47:                 Project Temp = new Project();
  48:  
  49:                 Temp.Includes = new List<string>();
  50:  
  51:                 Temp.Name = PathItems[0];
  52:  
  53:                 Temp.Includes.Add(Path);
  54:  
  55:                 Projects.Add(Temp);
  56:  
  57:             }
  58:  
  59:         }
  60:  
  61:         foreach (Project Project in Projects)
  62:  
  63:         {
  64:  
  65:             Utilities.IO.FileManager.CopyDirectory("Utilities directory" + Project.Name, "Utilities directory"+"." + Project.Name + "\\" + Project.Name, true, Utilities.IO.CopyOptions.CopyIfNewer);
  66:  
  67:         }
  68:  
  69:         foreach (Project Project in Projects)
  70:  
  71:         {
  72:  
  73:             if (Utilities.IO.FileManager.FileExists("Utilities directory"+"." + Project.Name + "\\Utilities." + Project.Name + ".csproj"))
  74:  
  75:             {
  76:  
  77:                 bool Changed = false;
  78:  
  79:                 string ProjectText = Utilities.IO.FileManager.GetFileContents("Utilities directory"+"." + Project.Name + "\\Utilities." + Project.Name + ".csproj");
  80:  
  81:                 XmlDocument ProjectDoc = new XmlDocument();
  82:  
  83:  
  84:  
  85:                 ProjectDoc.LoadXml(ProjectText);
  86:  
  87:                 XmlNamespaceManager Manager2 = new XmlNamespaceManager(ProjectDoc.NameTable);
  88:  
  89:                 Manager2.AddNamespace("Util", "http://schemas.microsoft.com/developer/msbuild/2003");
  90:  
  91:                 Nodes = ProjectDoc.DocumentElement.SelectNodes("//Util:ItemGroup/Util:Compile", Manager2);
  92:  
  93:                 foreach (XmlNode Node in Nodes)
  94:  
  95:                 {
  96:  
  97:                     string Path = Node.Attributes["Include"].InnerText;
  98:  
  99:                     if (Project.Includes.Contains(Path))
 100:  
 101:                     {
 102:  
 103:                         Project.Includes.Remove(Path);
 104:  
 105:                     }
 106:  
 107:                     else if (!Path.StartsWith("Properties"))
 108:  
 109:                     {
 110:  
 111:                         Node.ParentNode.RemoveChild(Node);
 112:  
 113:                         Utilities.IO.FileManager.Delete("Utilities directory"+"." + Project.Name + "\\" + Path);
 114:  
 115:                         Changed = true;
 116:  
 117:                     }
 118:  
 119:                 }
 120:  
 121:  
 122:  
 123:                 XmlNode Parent = Nodes[0].ParentNode;
 124:  
 125:                 foreach (string Path in Project.Includes)
 126:  
 127:                 {
 128:  
 129:                     XmlElement Node = ProjectDoc.CreateElement("Compile", "http://schemas.microsoft.com/developer/msbuild/2003");
 130:  
 131:                     Node.RemoveAllAttributes();
 132:  
 133:                     XmlAttribute Attribute = ProjectDoc.CreateAttribute("Include");
 134:  
 135:                     Node.Attributes.Append(Attribute);
 136:  
 137:                     Attribute.InnerText = Path;
 138:  
 139:                     Parent.AppendChild(Node);
 140:  
 141:                     Changed = true;
 142:  
 143:                 }
 144:  
 145:                 if (Changed)
 146:  
 147:                 {
 148:  
 149:                     ProjectDoc.Save("Utilities directory"+"." + Project.Name + "\\Utilities." + Project.Name + ".csproj");
 150:  
 151:                 }
 152:  
 153:             }
 154:  
 155:         }
 156:  
 157:     }
 158:  
 159: }
 160:  
 161:  
 162:  
 163: public class Project
 164:  
 165: {
 166:  
 167:     public string Name { get; set; }
 168:  
 169:     public List<string> Includes { get; set; }
 170:  
 171: }

Note that I've switched the directory for the Utilities project out for simply "Utilities directory" and also it takes advantage of the FileManager class in the utility library, so you can either rewrite those parts or just download the file from the link. Anyway, the code above gets the project file for Utilities (the non broken up version of the code), which is the csproj file.

The csproj file is an XML doc that contains the various references, files to include, etc. The nice thing about it is it's very easy to read and modify. In our case we want each of the items in ItemGroup/Compile. This section of the doc contains each document that needs to be included in the project. Since my library treats directories as namespaces (Utilities.Cisco items can be found in the Utilities/Cisco directory), I can simply separate out the items by directory (making sure not to have more than one instance of each project/namespace). Note that I'm only going one level down in this step, but if you wanted to create a project for every single namespace, you could do this as well.

At this point we know the directories and namespaces we want, so we simply copy over the existing files (if they've been changed anyway). The last step is to modify the csproj files for the individual projects. Once again we find the ItemGroup/Compile part of the XML doc and simply add the items that were missing, remove those that were removed, etc. and then save the final project file.

Once the app's set up, I simply set a post build command to run the app after Utilities compiles and that's it. Now at no point do I create a project file from scratch and add it to the solution. You could take this little app and do just that, but I'm happy with what it does at the moment. So if you use it as is, you're going to have to create the individual projects first but it will go through and split it up for you after that. So try it out, leave feedback, and happy coding.



Comments