部署完成打开页面,黄页上写着这么一段:
System.IO.FileLoadException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
0x80131040这个码的含义很直接:文件找到了,但里面记录的程序集版本跟请求的对不上,CLR宁可抛异常也不用它。请求的是Version=13.0.0.0,bin目录里实际躺着12.0.0.0,就是这段报错。编译能过、本机调试能跑,一到服务器就炸,是这个错的典型节奏。
还有两个跟它长得很像、处理方式完全不同的报错,先分清楚再动手。
FileNotFoundException:同样是Could not load file or assembly开头,尾巴变成The system cannot find the file specified。这是压根没找到文件,去查输出目录、GAC、以及发布时有没有漏拷。
BadImageFormatException,HRESULT是0x8007000B:位数不对,32位进程加载了64位dll,或者反过来。AnyCPU的主项目里某个依赖被编译成x86时最容易撞上。
把两边的版本号摆到一起
报错里给的是「期望版本」,还得拿到「实际版本」才知道差在哪。Windows下直接读dll文件里的程序集版本:
[System.Reflection.AssemblyName]::GetAssemblyName("D:\site\bin\Newtonsoft.Json.dll").Version
这里有个踩过不止一次的坑:NuGet的包版本和程序集版本不是一回事。Newtonsoft.Json 13.0.3这个包,AssemblyVersion是13.0.0.0;13.0.1也是13.0.0.0。bindingRedirect里照着NuGet面板上的版本号写newVersion="13.0.3",等于写了个不存在的版本,报错照旧。
程序集版本在项目里由AssemblyVersion特性控制,写成1.0.* 这种带星号的形式时,编译器会用当前日期时间生成后两位,每次编译都在变,这种项目最容易在别的机器上出现版本对不上。
先试统一版本,别急着写重定向
解决方案里几个项目各自引用了不同版本的同一个包,是这个问题最常见的根子。在解决方案上右键「管理解决方案的NuGet程序包」,Consolidate那一页会列出所有项目引用了哪些不同版本,一键合并之后重新生成,多数情况就结束了。
编译时MSBuild会给出警告MSB3277:Found conflicts between different versions of the same assembly。这个警告常年被淹没在输出里,实测在Visual Studio 17.11、17.12、17.13,这几个版本里,把「生成并运行」的输出详细程度调到「详细」,再搜MSB3277才能稳定看到;默认是「最小」,这个警告不显示。
确实要跨版本时,写bindingRedirect
有依赖死钉在旧版本上、又不能一起升级时,只能靠重定向把旧版本号映射到实际版本。写在入口项目的配置文件里,也就是生成后变成 .exe.config的那个App.config,或者Web.config:
<configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
三个容易写错的地方。publicKeyToken漏写会让整条重定向对强命名程序集直接失效,拿不准就用sn -T程序集.dll查,或者直接从报错原文里抄。oldVersion要写成区间0.0.0.0-13.0.0.0,只写一个版本时比它高的请求仍然会失败。newVersion必须是bin目录里那份的真实版本,不是包版本。
让MSBuild自动生成重定向
手工维护容易漏,项目文件里加一行就能让生成过程按输出目录的实况自动写:
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
Web项目在老模板里默认没开,所以 .NET Framework 4.5.1、4.6.2、4.7.2、4.8.1,这些目标框架的站点尤其要检查一遍。之前接手过一个跑在4.7.2上的老站点,把Newtonsoft.Json升上去之后线上直接报0x80131040,翻项目文件发现这一行一直是缺的,加上之后重新发布,重定向自动出现在Web.config里,问题就没了。
顺带记一下版本时间线:4.8是2019年4月发布的,4.8.1是2022年8月,也是Framework这条线的最后一版;4.6.2的支持要到2027年1月才结束,所以还在跑4.6.2的机器不用急着动。
改了配置没跟着上线
本机正常、服务器报错,第二高频的原因是配置文件没带过去,或者带的是旧版。App.config在编译后变成 程序名.exe.config,只拷dll不拷这个文件的发布流程,改了也白改。Web项目还有Web.Debug.config、Web.Release.config两道转换,发布时用Release配置生成,重定向有可能在转换环节被替换掉,去服务器上打开实际生效的那个Web.config看一眼最实在。
让CLR自己说它去哪找了
上面几步都对不上时,开绑定日志。fuslogvw.exe(Assembly Binding Log Viewer)在开发者命令行里直接敲名字就能启动,需要管理员权限。设置里选「将绑定失败记录到磁盘」,自定义一个日志目录,之后重启应用(IIS上要回收应用池,光刷新页面不一定重新加载),复现一次,双击日志能看到按顺序探测的每一个路径、每次探测的结果,以及最后判定失败的那一行。
碰到日志目录空空如也的情况,可以直接写注册表:HKLM\Software\Microsoft\Fusion下建ForceLog、LogFailures两个DWORD值设为1,再建字符串值LogPath指向日志目录。查完一定记得关掉,开着的绑定日志对性能有明显影响。
.NET Core之后不是这套机制
bindingRedirect在 .NET Core及其后续版本里完全不存在,程序集解析走的是输出目录里的deps.json加上runtimeconfig.json,版本不匹配时按rollForward规则往高版本滚动。所以同样的Could not load file or assembly报错,在 .NET 6.0、8.0、9.0、10.0,上通常不是版本冲突,而是这几种:包没被复制到输出目录、同一个程序集被Assembly.LoadFrom与默认上下文各加载了一份、插件目录里的dll与主程序引用的版本并存。
要控制滚动行为,在项目里放一个runtimeconfig.template.json写 "rollForward": "LatestMinor",或者临时用环境变量DOTNET_ROLL_FORWARD覆盖。见过一次比较隐蔽的现场:开发机装的SDK比服务器新,发布出来的deps.json指向服务器运行时里没有的补丁版本,开发机上一切正常,上线即报Could not load file or assembly,把两边SDK版本钉成一致就好了。
几条能省事的做法
解决方案里所有项目引用同一个包版本,用Consolidate一次性合并;用packages.lock.json把版本钉住,避免还原时悄悄拉到新的小版本;CI上把MSBuild的这个警告当错误处理(/warnaserror:MSB3277),冲突在编译阶段就暴露,别等到运行时;发布流程里把 .config文件和bin目录一起校验,缺文件直接失败。这些做完,0x80131040基本不会再出现。
数据截至2026年10月,涉及的 .NET Framework版本与Visual Studio版本号以微软官方下载页与发布说明为准。