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")
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 |
Here's the structure that Visual Studio creates at this point:
![]() |
Default project structure for a new .net Framework Console Application Project |
![]() |
The initial Solution-level folder structure of the new project |
![]() |
The Solution-level folder contains the packages folder that stores NuGet packages |
![]() |
bin and obj folders in the Project-level folder |
![]() |
Project properties with output path near the bottom |
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 |
- $(SolutionDir)
- $(Configuration)
- $(AssemblyName)
- $(MSBuildProjectName)
- etc.
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> |
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.)<UseCommonOutputDirectory>True</UseCommonOutputDirectory>
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 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> |
![]() | |
|
The solution folder is neat and orderly at this point:
![]() |
Solution-level folder structure |
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 modified Solution file |
![]() |
No src folder in the Solution Explorer |
![]() |
Invalid path to the packages folder after moving the Project file |
![]() |
The corrected path to the packages folder |
![]() |
Solution-level folder structure |
![]() |
Project-level folder structure |
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"?>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.
<Project>
<PropertyGroup>
<IntermediateOutputPath>$(MSBuildThisFileDirectory)\obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>
<OutputPath>$(MSBuildThisFileDirectory)\bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
</Project>
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:
- https://blogs.msdn.microsoft.com/kirillosenkov/2015/04/04/using-a-common-intermediate-and-output-directory-for-your-solution/
- https://github.com/FakeItEasy/FakeItEasy/blob/develop/Directory.Build.props
Thank you for reading this article. Should you have remarks, questions, or suggestions, feel free to leave a comment.
Comments
Post a Comment