Introduction
The issues discussed in this article refer to large, or very large Visual Studio .NET solutions, consisting of dozens of projects. This is often the case in teams where a team of programmers work on the same product. Usually, all the people deal with the same SLN solution file, which keeps growing in size (in the sense of number of projects, number lines of code, number of classes etc.).
In this article I will try to explain why having too many projects in a developer's solution is generally not a good idea. Then I will present the way to change it and, consequently, gain more time for productivity.
Disclaimer
Please note that I don't give hints on optimizing production builds (master solutions) here — I just describe how to adjust these solutions to the needs of every developer in the team.
Background
It is essential for the reader to distinguish between assembly references and project references in Visual Studio .NET.
A developer can decide over the type of reference when executing Add reference... command in Solution Explorer of Visual Studio .NET.
After clicking on Add Reference... command, an Add Reference dialog appears and a type of reference can be chosen. It can be either:
- A project reference (third tab) or,
- An assembly reference (fourth tab)
Adding a project reference is just choosing one of the projects that exists in a solution. Adding an assembly reference is just selecting a DLL file existing on disk (representing a proper .NET assembly). Certainly, there are classes that can't be referenced by means of project references, since nobody gives access to CSPROJ projects (and the source code) for them — for example, this is the case with all the .NET Framework classes.
The difference between both types of references can be summarized by the following table:
| Assembly reference | Project reference |
Free access to referenced source code (in order to view details or modify something) | No | Yes |
Referenced source code is rebuilt every time a solution is rebuilt (Rebuild Solution command is evoked) | No | Yes |
Referenced source code is built every time a solution is built (Build Solution command is evoked) | No | Only if modified or in the first Build Solution) |
As you can see, there is a speed vs. flexibility trade-off between the two types of references.
Why Smaller Solutions are Better?
As Visual Studio .NET solution grows in size, some problems arise:
- It takes significantly more time to load a SLN solution file into Visual Studio .NET
- Some Visual Studio .NET Add-Ins, applying advanced analysis on code (such as Jet Brains ReSharper) work slower (sometimes much, much slower)
- Finally, what is perhaps the biggest problem here, it can take several minutes just to rebuild such giants
(Referring to my professional experiences as a software developer: once I worked with a Visual Studio .NET solution that consisted of approximately 100 projects, which took 1 minute to load and 10 minutes to rebuild.)
What is Ultra?
Ultra is a name for a set of guidelines which can help significantly reduce the number of projects referenced by a Visual Studio .NET solution, while staying up to date with the master builds. Ultra is not a name for a technology or a product.
Assumptions
Just to make the text more concise, I assume the following:
- There is a SLN solution (noted as S and called as a master solution) consisting of N projects.
- S is used for making production builds.
- There is a master folder F where all the assemblies of S (the result of compilation of S) are being stored (usually it is \bin\Debug or \bin\Release).
- The relative location of folder F shouldn't change regarding the location of projects in the solution.
- All the projects in S reference each other by project references only.
- Every project is compiled to one assembly (DLL file). Every assembly represents only one project.
As an example, look at the following UML diagram:
- There are seven projects: A, B, C, D, E, F, G.
- A is the startup project for S.
- A references all other projects in S, as shown in the diagram.
The Steps
As you will see, Ultra can be described as the idea of:
- Using only these projects that are absolutely necessary in the solution
- Transforming the solution/projects ecosystem of a giant application into a new, smaller and independent layer, that is freely adjustable to the needs. This is strongly dependent on creating physical copies of solution/project files (see steps 1 and 3).
The steps below define precisely how to turn the idea into reality.
Disclaimer
Before continuing with the following steps, please create a backup copy of your solution. This is highly recommended!
1. Save the Master Solution SLN File as a Copy (Create Solution U)
- Load the SLN file of the master solution S into VS.NET
- Left-click on solution name in VS.NET Solution Explorer View
- Execute Save [solution name] As... command of VS.NET File menu command
- When Save File As dialog appears, enter any name you wish, for example: [solution name].ultra.sln. You can also consider naming the copy of the master solution as [solution name].[your nick].ultra.sln.
- Let's note the new solution as U (U for Ultra)
2. Identify Projects That Will Be Removed (R) from and That Will Be Left (L) in the Solution (U)
The solution U should be divided into two parts:
- Part R - the projects you decide to remove from the solution — usually the projects, that you don't modify/are not responsible for. In the example UML diagram below: R = {B, C, E, G}.
- Part L - the projects you want to leave in the solution — usually the projects, that you modify frequently. In the example UML diagram below: L = {A, D, F}.
Please note: no removals so far!
3. Create Copies of CSPROJ Project Files That Will Be Left (L)
For every project in L (the set of projects that you have decided to leave):
- Left-click on the project name in VS.NET Solution Explorer View
- Execute Save [project name] As... command from VS.NET File menu command
- When Save File As dialog appears, enter any name you wish, for example: [project name].ultra.sln. You can also consider naming the copy of the project as [project name].[your nick].ultra.sln).
4. Note the References In Projects That Will Be Left (L)
For every project in L, note its references.
From a practical point of view, you can just grab screenshots like this:
In the example: project WebcamTest
references WebCam_Capture
.
5. Remove the Projects Assigned to Remove (R)
For every project in R:
- Left-click on project name in VS.NET Solution Explorer View
- Press Delete key
- Confirm the removal
6. Restore Missing References In the Projects That Were Left (L)
For every project in L, restore its missing references by creating new assembly references to projects in R (if applicable). You should know what the correct references are, since you have noted them in step 4. The newly added assembly references should point to DLL files in master folder F - if F is empty, just rebuild the master solution S.
That's it ! Your first Ultra solution should now compile and run.
Staying Up-to-Date
During our work with solution U, it won't take too much time to make some of the referenced assemblies outdated, since other developers send their modifications to the source control. This is why master folder F has been introduced — in order to work with the latest state of the solution, it is enough to:
- Open master solution S
- Run the master build locally
If the master build succeeds at our computer, it should update assemblies in folder F. After opening our Ultra solution U in Visual Studio .NET, we should work with the latest state of the solution.
Flexibility
One might ask: "Wait a minute! It seems there is no possibility to predict all the necessary projects in advance. What if, say, I need to modify the set of projects in U a little bit?"
Well, I claim Ultra can be flexible. It is easy to add a new project P
to Ultra — just consider the following:
- Create a copy of CSPROJ file with renamed extension (e.g. [project name].[your nick].ultra.csproj) — if that file already exists, this step might be omitted
- Add the copy of CSPROJ to solution U (e.g. add [project name].[your nick].ultra.csproj to U)
- If P had been referenced by assembly references in U, change them to the project references.
To remove a project P from U, do the following:
- Start with step 2 (this time R = {P})
- Omit 3
- Proceed through 4 (you can also omit this step and identify the missing references by build errors)
- Proceed through 5
- Proceed through 6 — just add assembly references pointing project
P
assembly file in master folder F
Potential Problems (And How to Overcome Them)
One of the issues that might require special attention is adding new files to projects. For example, let's say I have two projects in my source code repository:
- ProjectA.csproj
- ProjectA.ultra.csproj
and I work with Ultra.
Now, if I add a new item to that solution,
say, a class, the addition won't be done automatically in the master solution. This might mean: if I don't do the same in the master solution (but, say, send the code to the source control server), the production build will get broken.
Special Case — Adding a New Folder and an Item
Now imagine the following scenario:
- I've added a new folder named New classes in my ProjectA.ultra.csproj
- I've added a new class and placed it in the folder
- Now, I want to do the same with ProjectA.csproj
When I open ProjectA.csproj, Visual Studio .NET won't make it possible to create the same folder again (A file or folder with the name 'NewOne' already exists on disk at this location. Please choose another name. ...
) The only way to achieve the goal is to modify ProjectA.csproj manually (in any XML file editor). Using the example above, the following line should be added:
<Compile Include="New classes\NewClass.cs">
in the appropriate section of ItemGroup
XML.
Conclusion
Even gigabytes of RAM and lots of GHz in CPU won't help too much when dealing with overgrown solutions. Removing nearly 80% of the projects from a large solution can make the difference, save your time and money — no need for hardware upgrades, really! The migration can be done once. Many "Ultras" can even coexist at the same time (used by you or other folks in the team) ! I believe the potential problems are identified and relatively easy to handle. Now, all you have to do is just try it yourself and see the everyday benefits of gaining some precious time.
History
- 8th of November 2007 - "The Steps" section improved
- 27th of October 2007 - First version created
Tomasz Szatkowski is a software developer working in the city of Gdynia, Tricity, northern Poland.
He is interested in pursuing ever evolving technology and applying it to everyday problems.
His most recent interest focus on Flex GUI interfaces, Microsoft WCF, middleware, SaaS model in software delivery.