I’m a big fan of project references in Visual Studio. They make development very easy and you never have to fight stale DLLs and wonder why on earth the code you just changed isn’t being reflected in a referencing project. But I’ve always had a gripe about how project references are set up – they are ignorant of which solution they are part of.
The problem surfaces when you are dealing with large solutions – 20 or more project files. With that many project files, things start building pretty slowly, and it would be great to turn off or flat-out remove some of the projects that you aren’t working on at the time. This is accentuated even more if your team does what I call “horizontal development.” In horizontal development, team members are responsible for horizontal slices of the application, as illustrated below.
With the horizontal approach (maybe this will be the topic of a later blog post?) shown here, Nathan never touches anything above the entities; Ben and Anthony never touch anything above Business Logic. Conversely, Marco never touches anything underneath the User Interface, and Pete never touches the Database and entities. Ideally, the projects these guys don’t ever touch could be excluded from the solution files they work in.
Assume we have a master solution as shown to the right. I’m using a simple example here and I removed the unrelated references. With this example, we can easily create a solution for Anthony and Nathan and include only the Entities and DataAccess projects in it. That works without any effort at all. But if we try to create a solution file that only includes WPFClient or one that has WPFClient and BusinessLogic, we hit a brick wall because of our project references. The WPFClient project file has project references, so those must be included in the solution. This means that even though Marco only changes the UI, he has to include all of the other projects in his solution and his builds take forever.
This problem has nettled me for years. While working on DASL during the peak of “Day 1” development, we had lots and lots of horizontal development going on and it would have been great for the UI guys to only have the UI projects in their solution. And we have lots of ancillary projects like windows services and console applications that were also relatively self-contained—it would have been nice for those to have project references to the Business Logic, but file references to the DataAccess. Couldn’t do it.
I’ve recently been doing some work on project files, solutions, and msbuild targets. While reviewing changes that a co-worker made to a project file, I noticed that he was doing conditional project references. Yes, project files support a Condition attribute on the elements. I thought about how this could apply to the horizontal development problem and I think it could work out well.
I put together a quick illustration to see if the approach would work. I have the master solution as shown above, but now I’ve created another solution as well; one that has only the WPFClient project in it. Off the bat, the solution wouldn’t build because WPFClient’s references were not satisfied. But with a little editing in the csproj file, I got it to work.
Here were the steps to make it happen.
- In the WPFClient subset solution, open up the Solution Configuration window
- As illustrated, click on <New…> for the WPF project configuration, and provide a name of “Debug.Client” and then select that.
- Close the solution configuration window, then right-click on the WPFClient project and select ‘Unload project’
- Right-click on the WPFClient project again and choose ‘Edit WPFClient.csproj’ to edit the project file within Visual Studio (this is a lesser-known trick itself)
At this point, we have created a project configuration for the WPFClient project named “Debug.Client” and we can use this as a condition. You want to find the following portion of the project file:
1: <ProjectReference Include="..\BusinessLogic\BusinessLogic.csproj">
5: <ProjectReference Include="..\DataAccess\DataAccess.csproj">
9: <ProjectReference Include="..\Entities\Entities.csproj">
We’re going to wrap those references in their own <ItemGroup> and copy the group to have 2 versions of it. One version will use project references as it does now, and the other version will use file references.
1: <ItemGroup Condition=" '$(Configuration)' == 'Debug.Client' ">
2: <Reference Include="BusinessLogic, Version=22.214.171.124, Culture=neutral, processorArchitecture=MSIL">
6: <Reference Include="DataAccess, Version=126.96.36.199, Culture=neutral, processorArchitecture=MSIL">
10: <Reference Include="Entities, Version=188.8.131.52, Culture=neutral, processorArchitecture=MSIL">
15: <ItemGroup Condition=" '$(Configuration)' != 'Debug.Client' ">
16: <ProjectReference Include="..\BusinessLogic\BusinessLogic.csproj">
20: <ProjectReference Include="..\DataAccess\DataAccess.csproj">
24: <ProjectReference Include="..\Entities\Entities.csproj">
At that point, you can save the project file, right-click on the WPFClient project and select ‘Reload Project.’ You can build the project in both the master solution and the WPFClient-only solution.
The “Debug.Client” configuration probably wouldn’t be best for large project though. Each one will be different—you’ll need to break down the different subsets that you want to support and come up with a short list of configurations that can be used to achieve what’s needed.
I hope this helps you save your developers some build time!