Structuring .net Framework Projects (bin and obj Folder Locations)

What is it all about?


Whenever I create a new project I feel that Visual Studio is just not doing what I want it to do. For some odd reason, the designers thought that my source code should be intertwined with the intermediate compilation results and the actual binaries. Visual Studio does provide the option to set the location of the output on the Build Properties tab of the projects, but the location of the intermediate output (the obj folder) is not so easily accessible. What bugs me even more, setting this up for a larger solution, where it could really shine is rather messy. While there is documentation available, it is tricky to get right - so here we go, summing it up.

Background

Back in the day when I worked at Morgan Stanley, we made quite a fuss about moving all our source code to some obscure "src" folder, and I could not for the life of me understand why we were doing it. Looking back a few years later, I totally get it.

Basically what I've grown to prefer is a uniform project structure, that looks like the following.
For each Solution, there are only three folders:
  • src, that contains the source code
  • bin, that contains the compiled binaries
  • obj, that contains the intermediate compilation objects
  • (Depending on personal taste, src might or might not contain test projects. In the latter case, there usually might be a fourth directory, named "tests")
All of the actual programmer-generated content is inside the src folder. All the projects reside there, but the compiler-generated artifacts are moved to a separate location. It's all nice and orderly, easy to navigate and to maintain.
The baseline for this discussion is the "old" MSBuild-based project file structure. Should you work on .net Standard, or .net Core projects, chances are you will find a rather different project file than what's described here. Still, you might want to take a glimpse at the end of this article, regarding the shared properties and the Directory.Build.props file, as that does work the same way in the new project structure - what's more, it's been extended with some rather neat features to help generate the AssemblyInfo.cs files to define your copyright and version information easily.

Why does this matter?


First of all, the content under source control is well separated from the content that is not under source control. Whenever I need to clean my project directory, I can safely and easily delete both the bin and obj folders. Whenever I want to revert all of my changes, I can be certain that I won't disturb my compiled artifacts. My ignore patterns are simpler. That's quite a maintenance performance boost.
Then there is the Java package file placement style: each namespace in an assembly corresponds to a folder, containing files within that namespace. Namespaces map to folders in the Solution structure. Should you want to find the location of a file containing a specific class in the repository, all you have to do is following the folder mapping of the namespace structure of the Solution, right until the last sub-folder, where the file with the same name as the class should reside. Now, what Visual Studio does by default is just pushing the compiled content right into the Project folder. I simply don't want my top level folders contaminated by the "bin" and "obj" folders, that certainly have nothing to do there.

How do I do it?

Let's walk through this, step-by-step, for a new Solution.

Creating a new .net Framework Console Application Project - and a new Solution to contain it
We simply create the Solution. No trick here.
Here's the structure that Visual Studio creates at this point:

Default project structure for a new .net Framework Console Application Project
There is a Solution, a Project, and one single source file, "Program.cs". I've done a build in Release configuration, just to have the output and the intermediate folders created for the Release build configuration as well. The resulting folder structure looks like this:
The initial Solution-level folder structure of the new project
There is a folder for my Solution, "SolutionStructureDemo", that contains the solution file itself, "SolutionStructureDemo.sln", one folder per Project, in this case, "ProjectStructureDemo", and a folder that stores some Visual Studio generated data (".vs"). Should I add some NuGet packages, another folder, called "packages" would appear:
The Solution-level folder contains the packages folder that stores NuGet packages
Everything's fine on this level, my issues lie one layer deeper, in the Project folder. My source code here is stored in the exact same folder as the compiler-generated stuff. Here comes the Java style of namespace to folder mapping, that is disturbed by the presence of the "bin" and "obj" folders..
bin and obj folders in the Project-level folder
Visual Studio makes it sort of easy to get rid of the "bin" folder. Right-clicking the project in the Solution Explorer window, and clicking Properties brings us to the project property configuration. The Build tab contains the Output group, where the output path can be set.
Project properties with output path near the bottom
However, as the latter part of the path suggests, this is a build configuration dependent setting. The active configuration is "Debug", thus the output path is "bin\Debug". As soon as the build configuration is changed to "Release", the output path also changes to "bin\Release". What's more, the location of the "obj" folder cannot be changed here.
A better solution to modify this is unloading the project, and editing the project file in Visual Studio. (The Power Commands for Visual Studio extension contains a menu item that allows editing the project file without unloading the project first.)
Project file edited in Visual Studio
The interesting parts are lines #21 and #31, the <OutputPath> tags, that contain the location of the compiled binaries. Any changes made on the Build tab of the Project Properties window will be reflected here. However, hand-configuring Debug/Release (and who knows what other build configurations you may desire) is really a mess there. Fortunately, MSBuild is clever enough to help us out here. There are a couple of placeholders that the build system can replace based on build configuration, such as:

  • $(SolutionDir)
  • $(Configuration)
  • $(AssemblyName)
  • $(MSBuildProjectName)
  • etc.
Using these placeholders, we can easily define a common path for our folders, regardless of project- and build configurations.
The value that I usually use is
$(SolutionDir)\bin\$(Configuration)\$(MSBuildProjectName)\

Thus the whole <OutputPath> tag (both in lines #21 and #31) looks like this:
<OutputPath>$(SolutionDir)\bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath>
Project file with a configured <OutputPath>
However, Visual Studio plays it safe and copies all binaries of all referenced projects to this folder. This might be a tad bit slow, and will certainly use crazy amounts of disk space - but we are fortunate enough to live in an age where this is the least of our concerns. Should it actually matter, there is an MSBuild switch to copy all the compiled binary output to one single folder:
<UseCommonOutputDirectory>True</UseCommonOutputDirectory>
Do note, however, that should there be two assemblies with the same name will cause a double-write race condition, that is, one of the files will overwrite the other, depending on the build order. That's generally not a good thing. (Yeah, I know, none of us would actually commit such a monstrosity, but then two different versions of the same NuGet package referenced in two different projects will do just that.)

So, then, how does the folder structure look now?

Upon rebuilding our Solution (and manually deleting our "bin" folders, as Visual Studio is not going to do that for us), we get to a state where there is exactly one "bin" folder in our Solution, on the top level. Yay!
One common bin folder with Build configuration and Project folders inside
This is somewhat better. Let's also move out the obj folder!

This one is just a tad bit more tricky. The Project Properties window has no options for this one, and the tag that we need is not included in the project file by default. However, the Visual Studio documentation lists quite a number of Common MsBuild Project Properties, and that list does contain the one we need here:
IntermediateOutputPath The full intermediate output path...
So, based on the <OutputPath> definition we had, our <IntermediateOutputPath> should look like this:
<IntermediateOutputPath>$(SolutionDir)\obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>
Magic happens in the two added lines, #21 and #32:
Project file with a configured <IntermediateOutputPath>
This will result in one Solution-level obj folder, with build configuration projects (Debug/Release) and Project folders inside. Dandy. After some more manual cleanup, and a new build (well, two, for the Release build), the folder structure looks like this:
One common obj folder with Build configuration and Project folders inside
No more generated files within our source.
The solution folder is neat and orderly at this point:
Solution-level folder structure
Well, at least up to the point where we start adding some more Projects.

I might have a slight OCD, but the three-letter bin and obj folders just invite the creation of a third folder to contain all that source code, say, how about src?

No fancy tricks here, just some manual folder creation, moving the files, and modifying the path to the Project file in the solution file. Do keep in mind, though, that creating a Solution Folder in the Solution Explorer window of Visual Studio does not create a physical folder in the solution folder - so this has to be done manually in file explorer.
The initial, unmodified Solution file
The project reference is right there in line #5. All we need to do is moving the whole folder into the src folder and modifying the path to the csproj file to contain the updated path. Here's how the modified file looks like:
The modified Solution file
Do notice that the Solution Explorer still looks the same at this point, without any src folder whatsoever - and that's good, we don't want artificial separation polluting our project structure.
No src folder in the Solution Explorer
However, as I have added a NuGet package earlier, and because of the (evil and naive) way NuGet references packages, our compilation fails at this point, as the packages are being referenced in the Project file as "../packages/...":
Invalid path to the packages folder after moving the Project file
Fortunately, we already know the solution to the issue: we simply need to search & replace "../packages/" with "$(SolutionDir)/packages/". Unfortunately, however, NuGet won't be clever enough to use this syntax, but at least the new NuGet packages will use the correct path, that is, they'll reference themselves moving two levels up from the Project folder.
The corrected path to the packages folder
After we are done with all this, we have a fancy folder structure:
Solution-level folder structure
Project-level folder structure
A TDD-related note might also belong here: creating a "test" folder on the top level, and moving test-related projects into that one as opposed to the "src" folder might be a good idea.

Are We Done Yet?

Yes*. Time to celebrate. (*: No, actually we are not.)

Okay, so basically we are done. However, as soon as we add a new project, we'll need to do all the project configuration over again for the new project. This pain can also be prevented by cleverly using shared properties, so let's see that last bit.
First of all, note that in order to do this, MSBuild tooling should be at or above version 15 (released with Visual Studio 2015). Older MSBuild toolsets might be able to pull something pretty similar off with the Common.props NuGet package.
MSBuild lets us extensively extend and customize our build process. The details are interesting and are certainly worth a read, but are outside of the scope of this article. The parts that are of interest to us are, however, as follows:
you can add a new property to every project in one step by defining it in a single file called Directory.Build.props in the root folder that contains your source. When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). If it finds one, it imports the property. Directory.Build.props is a user-defined file that provides customizations to projects under a directory.
So, all we need to do is creating a new file, called Directory.Build.props in our root folder, and setting up our build properties.
As mentioned at the beginning of the article, the new project system for .net Standard and .net Core hugely extends the usefulness of this file (and with a tiny bit of hacking - i.e. creating the project as a .net Core project and then changing the target framework to a .net Framework version, or simply waiting for Visual Studio 2019, where desktop application models will probably be ported to .net Standard).
Right here I'll stick to the topic of the article, and won't do a whole lot of magic with the common properties file, but things like common NuGet package references, code analysis rule sets, copyright information, and versioning information is only the tip of the iceberg here.
However, in order to get the output folders right, all we need to do is adding the following to our new Directory.Build.props file:
<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <IntermediateOutputPath>$(MSBuildThisFileDirectory)\obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>
    <OutputPath>$(MSBuildThisFileDirectory)\bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath>
  </PropertyGroup>
</Project>
No tricks here, it's exactly the same that we've added to our csproj files, just wrapped into a PropertyGroup and a Project tag.
Do note that if a Project contains definitions for the intermediate output path or the output path within the project file, that has precedence over the common properties that we have just defined, thus upon adding a new project, instead of adding our two new tags all we have to do is removing the two OutputPath tags from the project file.

Once again, Visual Studio 2019 promises to handle GUI-related projects with the .net Standard environment and the new project structure, so it might make all this way easier to handle.
Until then, however, we might event want to create a new project template based on the settings outlined here in order to easily replicate this with the least manual effort.

Apart from the inline links, consider checking out these articles:



Thank you for reading this article. Should you have remarks, questions, or suggestions, feel free to leave a comment.

Comments

Popular Posts