This is the first of a series of posts I will write about a very common problem faced by all sizable projects – how to structure your visual studio projects and resolve inter-dependencies between projects in different visual studio solutions. This is not an issue if you have just one visual studio solution as you can use project references across the board. However, if you have a number of solutions and there a developers working on them simultaneously than the dependencies can sometimes be crippling.
From my experience, the following structure is quite common for most projects.
The Platform Service solution contains common platform specific libraries like configuration, diagnostics, common controls, etc. The Domain Specific services contain libraries specific to the business domain such for example business specific libraries, protocols or schemas. Then there are vertical solutions on top.
In terms of visual studio solutions and project dependences, I would create a solution for each layer and the visual studio projects sitting in top layers are dependent upon projects in the lower layers.
Now that I have explained the context, let’s see different options we have in terms of setting up projects etc. The first and most common method is to set up a “DevBuild” i.e. a build script that builds all solutions in the order starting from the lower level solutions and culminating with compiling all the “verticals”.
All references within a solution are project references and references across solutions are file references. It’s best practice to reference the assemblies from a common “Assemblies” folder rather than the output path. Typically, you would want to do the copying of all assemblies after a build as a post-build activity. There are different ways of doing it. Below are some of the ones that I have used
1) Adding a custom .targets file in the within the $(MSBuildToolsPath)\Microsoft.Common.Targets\ directory.
This is my favourite and provides a clean “extension” point to add to the Visual Studio Build process. Visual Studio adds two folders within the MSBuild installation directory (C:\Program Files(x86)\MSBuild\12.0 for Visual Studio 2013). Within the directory, there are folders called ImportBefore and ImportAfter. You can write your own MSBuild code and store the file in either of the two folders. If you have extended or hooked into one of the targets run by Visual Studio as part of building a solution, your code will be executed. For example, the following code copies all the assemblies that starts with the name MyCustom and drops them to the common assemblies location for where they can be referenced by other solutions.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<DevelopmentRoot Condition="$(DevelopmentRoot) == ''">C:\Projects\MyCustom\Development\Main</DevelopmentRoot>
<Target Name="CopyAssemblies" AfterTargets="Build" Condition="'$(SkipCopyAssemblies)' != 'true'">
<MSBuild.ExtensionPack.Framework.MsBuildHelper TaskAction="RemoveDuplicateFiles" InputItems1="@(NetFilesToCopy)" Condition="%(NetFilesToCopy.Identity) != ''">
<Output TaskParameter="OutputItems" ItemName="NetFilesToCopyUnique"/>
<MakeDir Condition="!Exists($(AssembliesDirectory))" Directories="$(AssembliesDirectory)"/>
<Microsoft.Build.Tasks.Copy SourceFiles="@(NetFilesToCopyUnique)" DestinationFolder="$(AssembliesDirectory)" Condition="%(NetFilesToCopyUnique.Identity) != ''"/>
2) Using Post Build Event command
The post build events have been around since early releases of Visual Studio and allows you to run custom macros at the end of the build process. For me these are rather messy and interfere with your build process especially if you are using Team Build.
3) Visual Studio Add-on
Again a very messy approach but something we have used in the past. You can write your visual studio add-on that traps the compilation event and perform tasks such as copying files to a pre-defined location. I wouldn’t recommend it.
4) Use Visual Studio Solution extension points
When you build a visual studio solutions, behind the scenes, it generates and MSBuild file and uses the generated MSBuild file. In the generated MSBuild file, it includes two files called after.<SolutionName>.sln.targets and before.<SolutionName.sln.targets. If you have files with those names in the same directory and your solution files, it will become part of the build process and provides you an extension point. Sayed Ibrahim Hashimi has detailed it here.
The downside is that the extension points are only used when MSBUILD is called on the solution file (.sln) file from command line. Visual Studio doesn’t support it yet as detailed here.
For me Option 1 is still the best approach if you decide to go down the DevBuld route.
In my next post, I will write about NuGet and how it can be used as an alternative.