1

I have a C# project (ProjectA) that invokes another C# project (ProjectB) in a separate process. The build output directory structure is:

/ProjectA.exe
/ProjectB/ProjectB.exe

ProjectA and ProjectB reference difference versions of the same assembly, in this case Newtonsoft.Json.dll.

The build output directory structure is achieved by adding a nuget package for ProjectB to ProjectA. ProjectA and ProjectB are in separate solutions and built separately. The nuget package for ProjectB was created with the following .nuspec and .targets.

<?xml version="1.0"?>
<package>
  <metadata>
    <id>ProjectB</id>
    <version>$version$</version>
    <title>ProjectB</title>
    <authors>me</authors>
    <owners>me</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>ProjectB</description>
    <copyright>Copyright 2016</copyright>
    <tags>ProjectB</tags>
  </metadata>
  <files>
    <file src="x64\Release\*" target="build" />
    <file src="ProjectB.targets" target="build/ProjectB.targets" /> 
  </files>
</package>  

.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)*" />
    <Content Include="@(NativeLibs)">
      <Link>ProjectB\%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>

My problem is that ProjectA is referencing the newer Newtonsoft.Json.dll from the ProjectB nuget package /build directory instead of the older Newtonsoft.Json.dll that is part of the ProjectA solution. They are different versions so this is causing a problem at runtime. I realize I could just update the version of Newtonsoft.Json.dll in the ProjectA solution, but I want to be able to solve the more general case when that is not possible. How can I prevent Visual Studio from finding the wrong Newtonsoft.Json.dll?

Eric Roller
  • 429
  • 5
  • 19
  • IMO this package should reference the Nuget package for JSON instead, not an assembly. – aybe Apr 15 '16 at 17:04
  • ProjectA does reference the Json.Net nuget package (v7.0.1), just like ProjectB, but it is a difference version. Visual Studio is incorrectly using the newer version (v8.0.3) from the ProjectB nuget package /build directory. – Eric Roller Apr 15 '16 at 17:26
  • I suppose a bigger question might be: why do you have two different versions of the same DLL referenced within the same solution? This would certainly cause a version mismatch at runtime because you're only going to have one version sitting in the output directory, right? Perhaps you can solve it by separating the projects into separate solutions, or upgrading the one. – David Morton Apr 15 '16 at 18:33
  • The projects are in separate solutions. ProjectB is placed in a subdirectory so it can contain a different dll than in the parent directory which is used by ProjectA – Eric Roller Apr 15 '16 at 19:03
  • Don't put it in a subdirectory. If they're separate solutions, separate them into sister directories instead. – David Morton Apr 15 '16 at 19:04
  • I can put it in a sister directory by changing one of the lines in the .targets file from `ProjectB\%(FileName)%(Extension)` to `..\ProjectB\%(FileName)%(Extension)`. That doesn't change the problem with ProjectA referencing the dll in the ProjectB nuget package /build directory. – Eric Roller Apr 15 '16 at 19:48

2 Answers2

0

EDIT: This answer doesn't actually work. VisualStudio's behavior is very inconsistent with regard to which assembly it decides to use. Sometimes removing the assembly from the output directory will cause a change in the version of the assembly that ends up getting used. Apparently any assembly files that are linked to the project can be chosen by MSBuild during compile time. See my other answer instead.

I discovered that if I put all the ProjectB assemblies inside a separate subdirectory of the nuget package /build directory then MSBuild will not use them as references. The working nuget package for ProjectB was created with the following .nuspec and .targets.

<?xml version="1.0"?>
<package>
  <metadata>
    <id>ProjectB</id>
    <version>$version$</version>
    <title>ProjectB</title>
    <authors>me</authors>
    <owners>me</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>ProjectB</description>
    <copyright>Copyright 2016</copyright>
    <tags>ProjectB</tags>
  </metadata>
  <files>
    <file src="x64\Release\*" target="build/ProjectB" />
    <file src="ProjectB.targets" target="build/ProjectB.targets" /> 
  </files>
</package>  

.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)ProjectB\*" />
    <None Include="@(NativeLibs)">
      <Link>ProjectB\%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Apparently, MSBuild is looking in the same directory as the ProjectB.targets file for matching assemblies, but not subdirectories. This "magic" behavior from MSBuild should really be fixed by Microsoft. See related comment to this question

Community
  • 1
  • 1
Eric Roller
  • 429
  • 5
  • 19
0

In the end adding the linked item group with all the ProjectB assemblies was always causing problems with pulling in the wrong assembly version into ProjectA. I had to resort to a robocopy postbuild command to do what I wanted. This has the downside that the ProjectB assemblies do not get copied when ProjectA is referenced by another project, but that isn't critical for me. If Microsoft ever fixes this behavior with magic assembly paths then the nuget package in the original question should work fine. Here are the .nuspec and .targets files with the robocopy workaround.

<?xml version="1.0"?>
<package>
  <metadata>
    <id>ProjectB</id>
    <version>$version$</version>
    <title>ProjectB</title>
    <authors>me</authors>
    <owners>me</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>ProjectB</description>
    <copyright>Copyright 2016</copyright>
    <tags>ProjectB</tags>
  </metadata>
  <files>
    <file src="x64\Release\*" target="build/ProjectB" />
    <file src="ProjectB.targets" target="build/ProjectB.targets" /> 
  </files>
</package>  

.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="ProjectBCopyTarget" AfterTargets="Build">
        <Exec Command="robocopy /PURGE $(MSBuildThisFileDirectory)ProjectB $(TargetDir)ProjectB || if %ERRORLEVEL% LSS 8 exit 0"/>
    </Target>
</Project>
Community
  • 1
  • 1
Eric Roller
  • 429
  • 5
  • 19