Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSBuild is replacing Newtonsoft.Json.dll with an older version

I am using the MSBuild runner in TeamCity to build an ASP.net web api and running unit tests. Everything was working, until I upgraded to "Microsoft Build Tools 2017 15.7.2".

Suddenly msbuild was copying an older version of Newtonsoft.Json.dll (version 6.0.4.17603) from either "C:\Program Files (x86)\ISS\Microsoft Web Deploy V3" or "C:\Program Files\ISS\Microsoft Web Deploy V3" to the output folder when building the solution. All the projects are referencing the 9.0.1 version using NuGet.

Monitoring the output folder as the build was running, I could see the .dll switching back and forth between 6.0.4 and 9.0.1 until the build ended, and the 6.0.4 version remained.

I found this question and when I renamed the Newtonsoft.Json.dll files in the Web deploy folders to Newtonsoft.Json_old.dll", msbuild did not replace my 9.0.1 version and everything was working fine.

I have checked that all the projects referencing to Newtonsoft.Json are referencing the 9.0.1 version and using the correct Hint-Path in .csproj files.

Does anyone have any idea how to solve the problem? My solution seems more like a workaround and I would like to know why msbuild was copying this file in the first place.

like image 208
Eirik Fauske Avatar asked Jun 01 '18 07:06

Eirik Fauske


People also ask

Is Newtonsoft JSON obsolete?

The Newtonsoft. Json. Schema namespace provides classes that are used to implement JSON schema. Obsolete.


1 Answers

Summary

When MSBuild is resolving assemblies, it will search in some pretty weird directories, including that Web Deploy folder, depending on what you have installed. Based on the MSBuild reference, I believe that this is legacy behavior. You can stop it from doing that with an MSBuild property defined in your project file.

In the affected project file, find the following line:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

And add this below it:

<PropertyGroup>
    <AssemblySearchPaths>$(AssemblySearchPaths.Replace('{AssemblyFolders}', '').Split(';'))</AssemblySearchPaths>
</PropertyGroup>

This will cause MSBuild to no longer look in the problematic folders when resolving assemblies.


Full Story

My team ran into a similar problem when we moved to Visual Studio 2019. Some of our projects are still targeting .NET Framework 4.0, and after installing Visual Studio 2019 on our build agents, we started getting a mysterious error with projects that referenced some of our core libraries:

The primary reference "OurCoreLibrary, Version=3.4.2.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the assembly "Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed" which was built against the ".NETFramework,Version=v4.5" framework. This is a higher version than the currently targeted framework ".NETFramework,Version=v4.0".

The problem went away upon switching the project to target 4.5, but for reasons I won't get into here, we couldn't do that for every affected project, so I decided to dig in a bit deeper.

As it turns out, your question offered some insight into what was going on. The version of Newtonsoft.Json that we were referencing matched the version in "C:\Program Files (x86)\ISS\Microsoft Web Deploy V3", and when I removed the file, the build succeeded.

Our specific problem was that the copy of Newtonsoft.Json in the Web Deploy folder was the same version (9.0.0.0) but the wrong framework (4.5 instead of 4.0), and for whatever reason the resolution logic doesn't check the target framework, causing a mismatch at build time. Updating to VS2019 involved updating Web Deploy, which also updated that copy of Newtonsoft.Json to 9.0.0.0, causing our collision.

To see why that assembly was even being looked at to begin with, I set the MSBuild project build output verbosity to Diagnostic and took a look at what was happening. Searching for the offending path showed that in the ResolveAssemblyReferences task, MSBuild was going through some unexpected places to find matches:

1>          For SearchPath "{AssemblyFolders}". (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)

Further digging shows that the paths searched are passed in as AssemblySearchPaths, which is defined in Microsoft.Common.CurrentVersion.targets:

<AssemblySearchPaths Condition=" '$(AssemblySearchPaths)' == ''">
  {CandidateAssemblyFiles};
  $(ReferencePath);
  {HintPathFromItem};
  {TargetFrameworkDirectory};
  $(AssemblyFoldersConfigFileSearchPath)
  {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)};
  {AssemblyFolders};
  {GAC};
  {RawFileName};
  $(OutDir)
</AssemblySearchPaths>

According to the MSBuild Task Reference for the ResolveAssemblyReferences task, SearchPaths parameter is defined as:

Specifies the directories or special locations that are searched to find the files on disk that represent the assemblies. The order in which the search paths are listed is important. For each assembly, the list of paths is searched from left to right. When a file that represents the assembly is found, that search stops and the search for the next assembly starts.

...and it defines a few special constants, including our friend {AssemblyFolders}:

  • {AssemblyFolders}: Specifies the task will use the Visual Studio.NET 2003 finding-assemblies-from-registry scheme.

Because the directories are checked in order, you might expect {HintPathFromItem} to take precedence, and in most cases it does. However, if you have a dependency with a dependency on an older version of Newtonsoft.Json, there won't be a HintPath for that version and so it will continue on until it resolves.

Later on in Microsoft.Common.CurrentVersion.targets we can see that there are cases where this constant is explicitly removed, which is where the answer above comes from:

<PropertyGroup Condition="'$(_TargetFrameworkDirectories)' == '' and '$(AssemblySearchPaths)' != '' and '$(RemoveAssemblyFoldersIfNoTargetFramework)' == 'true'">
  <AssemblySearchPaths>$(AssemblySearchPaths.Replace('{AssemblyFolders}', '').Split(';'))</AssemblySearchPaths>
</PropertyGroup>

Removing this constant removes the offending folders from consideration, and to be honest I cannot think of a situation where I would want an assembly to implicitly resolve to whatever version of say, Newtonsoft.Json, was hanging out in the Web Deploy or SQL Server SDK folder. That being said, I am sure there's a case out there where turning this off will cause somebody issues, so keep that in mind.

like image 90
Scott Baldwin Avatar answered Sep 19 '22 15:09

Scott Baldwin