I’m sure I’m not the only experienced C# developer who finds the classic csproj file format to be more than a little inscrutable - difficult to read and understand.

If I could have completely ignored the content of every csproj file I would have. Unfortunately, I’ve found that source control merge conflicts are a common occurrence, and it is, therefore, next to impossible to avoid delving into their depths.

Fortunately, there’s a new, simpler, csproj file format that’s actually easy to read and understand. Not only are the files are much (much!) shorter, the chances of a merge conflict are near zero because you can now use a wildcard to pick up all the files in a directory. No more need to list every file by hand!

I recently converted a non-trivial personal project (18 projects, 534 source files, 32 kloc) across to the new file format and found it wasn’t difficult to achieve in a couple of evenings of effort.

Preparation

As a starting point, you should read this upgrade guide from Nate McMaster which goes into all the gory details.

Also useful is this StackOverflow question - the answers go into some detail about the required changes and suggest some other tooling too.

It’s also worth reading the official documentation that describes the changes; however, the post does assume a fair amount of prior knowledge of MSBuild and CSProj files.

You do need to understand much of this if you want a successful conversion - none of the available tools does a totally reliable job.

A final warning before we start: these are wide reaching changes to the way your projects are compiled. Make sure you have an easy way back, using your source control system, just in case the conversion is a failure.

Automated Conversion

I used the accurately named csproj-convert to do an initial pass on conversion. Written in F#, the source code is relatively short and quite easy to parse - even for someone like me who has only a passing knowledge of the language.

The conversion isn’t 100% so you will have to do some of the work yourself, but it certainly saves a lot of time and effort.

Manual Fixup

Once the automated conversion is complete and everything is building properly, there are a few additional changes you might want to make manually.

Use Wildcards

You can delete all of the <ItemGroup> lines that explicitly list .cs files to compile and replace all of them with this single wildcard:

  <ItemGroup>
    <Compile Include="**\*.cs" />
  </ItemGroup>

This is immensely useful because it practically eliminates the possibility of merge conflicts on this file.

Warning: This changes the master list of source files from “What’s listed in the csproj file” to “What’s present in the folder”. If there are any extra files in the folder (say, old files that are no longer needed but which haven’t been deleted), this wildcard will pick them up, try to compile them, and probably generate some errors.

Update, 8 July 2018
Explicitly listing wildcards like this has some problems - but there's a better way. See my followup post Default includes and excludes for csproj for details.

In my case, I found I needed to do some minor cleanup in each project. This was stuff that I should have done earlier but hadn’t - so introducing the wildcard forced my hand a little.

You will also need to delete any AssemblyInfo.cs files as this information is now automatically generated from the csproj file.

Tidy your ItemGroups

I rearranged the <ItemGroup> elements in my files to group together similar items - all of my <Reference> in one group, all <PackageReference> in another, and so on. This makes the file easier to scan.

Automated builds

I thought I had a successful conversion because everything was compiling correctly in Visual Studio 2017.

But when I tried my automated build script and discovered nothing would build - I got this error a lot:

...\Microsoft.PackageDependencyResolution.targets(327,5): 
error : Assets file '...\Document.Factory.Core\obj\project.assets
.json' not found. 
Run a NuGet package restore to generate this file. [...\Document.Factory.Core.csproj]

(I’ve elided some paths to make the message easier to read.)

After some intensive research, I discovered that the classic .csproj file format automatically restores NuGet packages as a part of the build. This doesn’t happen with the new .csproj file format and you need to do it yourself.

I quickly discovered that the obvious solution (using dotnet.exe) didn’t work. Neither did nuget.exe when I tried it.

Knowing that others must have solved the same problem, I kept researching.

The solution? The appropriate targets are available when compiling with msbuild.exe, but aren’t invoked by default, so I updated my build script to do this as a prior step:

Task Restore.Packages -Depends Requires.MSBuild {
    exec {
        & $msbuildExe /verbosity:minimal /t:Restore Document.Factory.2.sln 
    }
}

If you’re not using psake for your builds, you can ignore everything other than the line containing $msbuildexe and the command line parameters shown there.

Comments

blog comments powered by Disqus