This post is over 6 months old. Some details, especially technical, may have changed.

An Opinionated Project Structure

Choice and flexibility are good, in fact they a super smashing awesome. Sort of. I mean if you go over the top and offer people "all the choices" they are going to get all stressed and probably procrastinate until someone else makes the decision for them. Also if you give them so much flexibility they will probably do things differently each time - again that is not a great thing either - for many things in life and IT you shouldn't have to make a conscious decision (it's probably waste of time in this case).

I've recently been reviewing my companies default project structure and it's full of choice and flexibility and while this can cater for the vast array of different project types in our company it also imposes a certain level of cognitive-tax prior to kicking off any development to get the solution in a position that can be farmed out to the team. It imposes this tax across ALL projects even the 99% of the time typical project, with their typical project structure. It's not a costly tax but it's unnecessary. Why not just push it onto the non-standard projects who will likely need to spend time setting things up correctly anyway? Suck it, weird projects! Thats what I say.

To this end I've been trying to build a fairly opinionated project structure (along with some handy tools during the development stage). Right now it's in the early stages and open to abuse/commentary (in fact I'd welcome it, please) but I thought it makes sense to share it1 - for the benefit of myself and others (or selfishly unselfish as I like to call it).

Folder Structure

I didn't want to break from tradition here. There is exists a fairly standard project structure that is common across many languages and environments and rather than create a clever, verbose structure I reckon it's probably best to stick with the tried and tested approach. That way new developers, regardless of background, shouldn't have to wrestle with heavily nested folders or obscure names for folders (SolutionSource vs src) when navigating our solution.

|_ <MySolution>
    |_ <lib>
   |_ repositories.config
    |_ <src>
    |_ <test>
 |_ .gitignore
  |_ default.ps1
  |_ MySolution.sln
 |_ nuget.config

Most of this will be fairly self-explanatory2 but whats the harm in a little clarification?

  • lib this is the place either myself or Nuget (see nuget.config and default.ps1) will dump any DLLs, tools or other dependencies required by the solution. As I will talk about later the only file I ever check in here is the repositories.config and have nuget resolve the rest (still some decisions need made around manually referenced files etc.).
  • src hold all the projects that are actually going to be released as part of the final solution. This folder doesn't contain any of the test projects you create for the solution - no point in muddyinbg the waters.
  • test holds all the solutions projects that we create for running tests (unit, integration etc) to validate the solution.

Special Files

My default project comes with a number of files that help make developing and building the solution form scratch as easy as it should be. These files are open to be tweaked to suit the needs of the project as and when necessary.

repositories.config

This is a file that is used by Nuget to resolve the location of all the package configuration files associated with the projects in a solution (packages.config). When I start this file is empty but as dependencies are added via Nuget this file is updated to the location of packages.config files within each project (which in turn is used to resolved the necessary Nuget packages that need to be downloaded). I include this file along with a download task in my psake build file (see later) so that we we don't have to check-in all those, potentially large, dlls and tools into our VCS - it's slow and annoying. As a bonus when using some VCS's (e.g. git) this file will ensure the lib folder is committed to source control even thought it is technically empty - keeping our project structure intact.

.gitignore

Used to ignore certain generated and user specific files when using the git version control system. Keeps things nice and clean and reduces annoying unnecessary conflicts. As with most solutions out there I ignore the following patterns by default,

bin
obj
*.suo
*.csproj.user
*.cache
lib/*/

nuget.config

I'll jump onto the meat of the files, default.ps1, shortly but first as you can probably guess by now I assume the use of Nuget. It's there, it's great and thats all I want to say on that matter. nuget.config is a simple file that tells Nuget (even within Visual Studio) to put all it's downloaded files into the lib directory rather than the default packages folder. Everyone understands what lib is, less people - packages.

<settings>
 <repositoryPath>lib</repositoryPath>
</settings>

default.ps1

I've decided to run with psake to coordinate and perform my builds. MSBuild, while useful, is ridden with sickeness I call XML (BTW everyone else calls it XML too AFAIK). It's a beast to get right. Psake on the other hand is pure script. It's more succinct and you can even debug it - how awesome is that? So default.ps1 is my psake based build file. What does it look like currently? Glad you asked.....

# --
#                     S C R I P T   P R O P E R T I E S 
# --
Properties {
 $solution = (Get-ChildItem *.sln).Name
  $solutionname = $solution.Substring(0, $solution.LastIndexOf('.'))
}

I assume that the majority of projects are based on a single solution held at the root of our folder structure (as described above). So I dynamically grab the name and relative path of the solution file for use later.

# --
#                      H E L P E R   F U N C T I O N S
# --
function Write-LineBreak(){
  Write-Host "-"
}

Set of helper functions currently only provides a simple function to print out a flowerboxing line to make output a bit cleaner.

# Set the default framework (3.5 by default)
$framework = '4.0'

Assume that we are all living in the present (as much as possible) so switch to .NET 4.

# Declare default task
Task Default -depends Build,Test

By default psake should do a Build and Test.

# Resolves all the requires nuget dependecies so they don't need checked in
Task Resolve {
  Push-Location '.\lib\'
  Get-Content '.\repositories.config' | foreach {
 if($_ -match 'path="(.*)"'){

      Write-LineBreak
     Write-Host "Resolving Dependencies for " -nonewline
     Write-Host $matches[1].Split('\')[-2] -ForegroundColor Green
      Write-LineBreak

      nuget install $matches[1]

      Write-LineBreak
 }
  }
  Pop-Location
}

Resolve is the function mentioned earlier that finds the location of the repositories.config files and asks Nuget to resolve all the external dependencies of the solution. This should be done when you have pulled from a repository that included new dependencies.

# Build the first solution file you find
Task Build {

  Write-LineBreak
  Write-Host "Building Solution " -nonewline
  Write-Host $solutionname -ForegroundColor Green
  Write-LineBreak

  Exec { msbuild   /v:quiet /t:Rebuild  }

  Write-LineBreak
}

Performs a basic, quiet build of the default solution.

# Run the project tests
Task Test {
}

Executes all the tests in the solution. Currently empty I intend to flesh this out with a default unit testing approach.

Baby Steps

So this is just the beginning and I hope this evolves based on observations made in our real world projects rather than a bunch of nice to haves. It will also be nice to automate the creation of projects and solutions but thats certainly for another time. Recommendations, comments and all that stuff welcome. I'll try and get this basic structure up on Github too.

1 I mean, it has been a while since I blogged so it's about time I get back to it. That said this isn't a "filler post". I'm not doing this under some random contract. Thought I wish I was - imagine that - how fun.

2 Cracker idea, right? Who'd have thought it??! I'm a total genius and you can bow at my feet at will please. kthnxbai.

3 All subfolders in lib - as mentioned in the previous section.

Published in .NET on August 15, 2011