Your project dependencies matter (when using NuGet)

At work we had the following issue, we had a Silverlight project consuming some NuGet package, this project was being exposed in a Web application.

In order to keep our solution clean – we didn’t want an ‘assemblies’ folder – we used NuGet package restore.

We did it like the book said, don’t check in the packages folder, enable NuGet package restore, add the required repositories to the nuget.targets, you know the drill.

When building locally we didn’t have any issues. Everybody was able to run the project perfectly. The packages were downloaded from the repository when unavailable locally.

The issue rose when we committed our code to our source control system and ran builds of it with MSBuild.

In order to explain my issue I managed to create a very simple test case.

I created a solution with 2 projects, a web project, and a Silverlight project.

Solution window

The NuGetTest.Client had a NuGet reference to the package ‘MvvmLight’.

When you commit code to source control and run builds of it with MSBuild, it is supposed to download the NuGet package, and then build of that.

However, if we invoke the build we are presented with the following error:

Ugh, error.

What? Why?

So I went to check the packages folder on the Build location:

It's there!

It’s there! How could it not find the dll?

Time to dig deeper.

The command line executed for the project was simple:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /nologo /noconsolelogger "C:\Builds\1\NuGetTest\New Build Definition 1\Sources\Dev\NuGetTest.sln" /m:1 /fl /flp:"logfile=C:\Builds\1\NuGetTest\New Build Definition 1\Sources\Dev\NuGetTest.log;encoding=Unicode;verbosity=diagnostic" /p:SkipInvalidConfigurations=true  /p:OutDir="C:\Builds\1\NuGetTest\New Build Definition 1\Binaries\\" /p:VCBuildOverride="C:\Builds\1\NuGetTest\New Build Definition 1\Sources\Dev\NuGetTest.sln.vsprops"  /dl:WorkflowCentralLogger,"C:\Program Files\Microsoft Team Foundation Server 2010\Tools\Microsoft.TeamFoundation.Build.Server.Logger.dll";"Verbosity=Diagnostic;BuildUri=vstfs:///Build/Build/8;InformationNodeId=622;TargetsNotLogged=GetNativeManifest,GetCopyToOutputDirectoryItems,GetTargetPath;TFSUrl=http://localhost:8080/tfs/DefaultCollection;"*WorkflowForwardingLogger,"C:\Program Files\Microsoft Team Foundation Server 2010\Tools\Microsoft.TeamFoundation.Build.Server.Logger.dll";"Verbosity=Diagnostic;"

I slimmed it down to the real deal (removed logging), and ended up with this:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "C:\Builds\1\NuGetTest\New Build Definition 1\Sources\Dev\NuGetTest.sln" /m:1 /p:SkipInvalidConfigurations=true

Now I download the latest version of the project locally, and execute that command line:

"d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.sln" (default target) (1) ->"d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Host\NuGetTest.Host.csproj" (default target) (2) ->"d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Client\NuGetTest.Client.csproj" (default target) (3:2) ->(CoreCompile target) ->  ViewModel\MainViewModel.cs(1,7): error CS0246: The type or namespace name 'GalaSoft' could not be found (are you missing a using directive or an assembly reference?) [d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Client\NuGetTest.Client.csproj]  ViewModel\MainViewModel.cs(17,34):errorCS0246: The type or namespace name 'ViewModelBase' could not be found (are you missing a using directive or an assembly reference?) [d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Client\NuGetTest.Client.csproj]  ViewModel\ViewModelLocator.cs(15,7): error CS0246: The type or namespace name 'GalaSoft' could not be found (are you missing a using directive or an assemblyreference?)[d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Client\NuGetTest.Client.csproj]

 

And we’re greeted with a lot of text, and at the end the same error. So it’s not the user that executes the build (he has sufficient rights).

What seems to happen here?

If we check the log we can see that the NuGetTest.Client is built because NuGetTest.Host depends on it:

Project "d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Host\NuGetTest.Host.csproj" (2) is building "d:\Personal\Documents\Visual Studio 2010\Projects\NuGetTest\NuGetTest.Client\NuGetTest.Client.csproj" (3) on node 1 (GetXapOutputFile target(s)).

If we then continue reading the log file we can see that NuGet’s RestorePackages task is only executed after this task, which results in MSBuild trying to build the Silverlight project before all the required assemblies are downloaded.

How can we solve this? We can specify that the Silverlight project should be built before the web project, and thus forcing the RestorePackages to execute in order (before the build of the Silverlight project).

We can do this by going to project dependencies in the Solution Explorer, just do a right mouse on your solution and go to “Properties”.

In that window go to “Project Dependencies”:

Properties for solution, build order

In the “Project” dropdown you select your host project (here “NuGetTest.Host”), and check the checkbox of every Silverlight Project it depends on. This will make MSBuild explicitly build that project before continuing to the web project.

Good luck & have a good one!

Thoughts on a Resharper 6 refactoring

Consider the following code:

var foo = new List() { /* ... */ };

If we want to know if there are no items in the list we have 3 possibilities (probably more, but I’ll limit myself to 3 possibilities in this scope).

bool listEmpty = foo.Count == 0;

Which just uses List’s implementation of ICollection.Count.

Second option is:

bool listEmpty1 = foo.Count() == 0;

Last option is:

bool listEmpty2 = foo.Any();

As you might know, since ReSharper 6, the second option is suggested to be refactored to the last option.

image

However it is not the same to do an Enumerable.Count() and a Enumerable.Any() on an ICollection.

If we take a closer look using a decompile tool (Reflector, Resharper Decompile, JustDecompile, …) at the Count() and Any() extension methods we see the following:

Count:
image

Any:
image

We can clearly see that when using the Count() extension method the system actually first tries to see if the the IEnumerable<TSource> is an ICollection<TSource> or an ICollection, and using the Count property if possible, before enumerating over the entire list.

So I say: not everything Resharper says is correct, always use it carefully, think before you do an automatic refactoring. And more importantly: think about the design of your code. Is it needed that you pass in an IEnumerable? Is an ICollection<TSource> or an IList<TSource> more useful?

Have a good one!