MSBuild从非packages路径复制NuGet依赖DLL致TeamCity构建失败
问题背景
- 基于*.NET Framework 4.6.2*开发的C# WebApi应用,通过NuGet传递依赖引入
System.Runtime.InteropServices.RuntimeInformation (v4.3.0),该包是Microsoft.CodeAnalysis.Razor 2.2.0、Microsoft.DotNet.PlatformAbstractions 2.1.0的自动安装依赖。 - 本地环境构建的产物可正常运行,另一台机器上的TeamCity服务执行自动构建后,程序启动报错:
Could not load file or assembly 'System.Runtime.InteropServices.RuntimeInformation, Version=4.0.2.0...'
初步排查结果
对比两侧构建产物、NuGet缓存文件后,确认存在以下差异:
- 本地
bin/debug目录下的System.Runtime.InteropServices.RuntimeInformation.dll文件版本为4.6.26011.1,修改时间为2021年8月10日,对应产物运行正常 - TeamCity构建产物中同名dll文件版本为4.6.24705.1,修改时间为2016年5月11日,对应产物启动失败
- 本地与TeamCity服务器的
packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0目录下,NuGet包自带的dll版本均为4.6.24705.1,修改时间为2016年5月11日
初始疑问
- 本地packages目录下该dll版本为4.6.24705.1,为何构建输出到bin目录的版本为4.6.26011.1?MSBuild是否从packages以外的路径复制了该依赖?此前全盘搜索未找到本地存储的4.6.26011.1版本该dll。
- 如何监控MSBuild执行流程,定位本地构建时该dll被复制到
bin/debug目录的具体来源路径? - 如何调整引用配置,保证不同环境下构建的程序均可正常运行?
2022年6月21日进展更新
经排查确认,本地构建时该dll是从JetBrains Rider 2021.2.2自带的MSBuild扩展路径C:\Program Files\JetBrains\JetBrains Rider 2021.2.2\tools\MSBuild\Microsoft\Microsoft.NET.Build.Extensions\net462\lib\复制到bin目录的,且仅该依赖取自该路径,暂不清楚MSBuild选用该路径的触发逻辑。
补充说明:项目引用了Kestrel等AspNetCore组件,同时适配.NET Standard 2.0,初步怀疑该问题与.NET Framework引用.NET Standard程序集时的构建扩展机制有关,已检索到同类问题反馈但暂未明确对应解决方案。
解决方案
MSBuild依赖来源定位方法
要定位构建时dll的复制来源,直接开启诊断级构建日志即可,不需要全盘搜索文件:
- 执行构建命令时附加以下参数:
msbuild [你的项目文件路径].csproj /t:Rebuild /v:diagnostic /fl /flp:logfile=build_detail.log - 构建完成后打开生成的
build_detail.log,搜索System.Runtime.InteropServices.RuntimeInformation.dll,找到对应的Copy任务条目,会明确标注该文件的源路径、目标路径以及触发复制的判断逻辑,可100%定位文件来源。
你遇到的Rider扩展路径优先的问题,是.NET Framework引用.NET Standard 2.0程序集的默认构建机制导致的:
当MSBuild检测到项目依赖.NET Standard 2.0程序集时,会自动加载Microsoft.NET.Build.Extensions扩展,优先从扩展目录下复制适配当前.NET Framework版本、修复了跨框架兼容性问题的系统基础库,覆盖NuGet包中自带的旧版本,解决跨框架的类型转发、版本匹配问题。你本地Rider自带的扩展目录里是2021年更新的修复版dll,TeamCity构建节点用的是旧版MSBuild/Visual Studio Build Tools,没有对应版本的扩展文件,就回退到复制NuGet包里2016年的旧版dll,这个旧版本缺少正确的版本绑定配置,运行时找不到4.0.2.0版本对应的类型转发记录,就会抛出程序集加载失败的错误。
跨环境构建一致性修复方案
三个方案选一个即可,优先选择前两个,稳定性更高:
- 方案1:统一构建环境版本(最稳妥)
在TeamCity构建节点安装和本地开发环境版本一致的MSBuild/Visual Studio Build Tools,保证所有构建环境的.NET Build Extensions版本完全对齐,从根源上消除dll版本差异,不会引入额外的兼容性问题。 - 方案2:显式固定依赖+配置绑定重定向
- 不要依赖传递引用引入
System.Runtime.InteropServices.RuntimeInformation,手动在项目中安装该包的4.3.0或更高稳定版本,显式指定引用版本号 - 打开项目
web.config,在<runtime>/<assemblyBinding>节点下添加该程序集的绑定重定向,也可以直接在项目属性中开启「自动生成绑定重定向」,让构建过程自动生成匹配的版本映射规则,参考配置:<dependentAssembly> <assemblyIdentity name="System.Runtime.InteropServices.RuntimeInformation" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" /> </dependentAssembly>
- 不要依赖传递引用引入
- 方案3:禁用扩展自动覆盖逻辑(不推荐)
在项目.csproj文件的首个<PropertyGroup>节点下添加<DependsOnNETStandard>false</DependsOnNETStandard>,强制MSBuild不走Build Extensions的覆盖逻辑,所有系统依赖统一从NuGet包目录取。该方案需要全量回归测试所有.NET Standard相关依赖的加载逻辑,容易引入其他类型加载异常。
内容的提问来源于stack exchange,提问作者Jan Veselý

