dotnet pack 打包文件版本号引起 “Could not load file or assembly” 问题

C#

浏览数:242

2019-5-20

AD:资源代下载服务

如果不是遇到,真的不会想到,代码世界的问题真是千奇百怪,这次遇到的是 dotnet pack 打包文件版本号引起的问题。

之前进行 nuget 打包都是在 Visual Studio build 时进行,版本号时通过 .csproj 中的 VersionPrefix 指定,没遇到问题。

最近,改为通过 shell 脚本在 linux 上打包,开始的 shell 脚本是怎么写的:

dotnet pack -c Release /p:version=$(git tag --sort=committerdate | tail -1 )

后来发现 dotnet pack 不能自动 build 项目,这个问题不是从哪个版本的 .net core cli 开始出现的。

为了解决这个问题,在 dotnet pack 命令之前添加了 dotnet build 命令:

dotnet build -c Release
dotnet pack -c Release /p:version=$(git tag --sort=committerdate | tail -1 ) 

采用这个脚本发包后,最近几次发包遇到了奇怪的问题,比如发布了 EnyimMemcached 2.2 包,在引起该包的 A 项目中升级到 EnyimMemcached 2.2,而 A 项目所引用的其他 nuget 包也引用了 EnyimMemcached 但引用的是旧版 EnyimMemcached 2.1.12 ,build 没问题,运行时却报下面的错误,整个应用都无法启动。

System.IO.FileLoadException: Could not load file or assembly 'EnyimMemcachedCore, Version=2.1.12.0, Culture=neutral, PublicKeyToken=null'. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'EnyimMemcachedCore, Version=2.1.12.0, Culture=neutral, PublicKeyToken=null'
   at System.Signature.GetSignature(Void* pCorSig, Int32 cCorSig, RuntimeFieldHandleInternal fieldHandle, IRuntimeMethodInfo methodHandle, RuntimeType declaringType)

对于这样的依赖同一个包的不同版本问题,.net core 中已经进行了有效解决,如果存在版本冲突,在 build 时就能发现,比如 解决 .net core 中 nuget 包版本冲突问题 ,只要 build 通过,基本不会出现上面的问题,.net core runtime 会自动使用最高版本。

而我们现在却遇到了这个 .net core 已经解决的问题,让人莫名其妙,无从下手。今天再次遇到这个问题时,想到把包解开看看其中有没有与运行时相关的元数据信息。

解开一看,除了 dll 文件,没有其他用于运行时的文件,在查看这个 dll 文件的信息时发现了线索:

 

不对,文件版本号怎么是 1.0 ,打的明明是 2.2 的包?

Review 打包脚本后恍然大悟

dotnet build -c Release

dll 文件是在 build 时生成的,Build 时没有添加版本信息,于是使用了默认版本号1.0。

赶紧改进脚本试试

VERSION=$(git tag --sort=committerdate | tail -1)
dotnet build /p:version=$VERSION -c Release Enyim.Caching
dotnet pack -c Release /p:version=$VERSION Enyim.Caching

这样改进后,打出的包中的 dll 文件版本就与包的版本就一致了

在项目中升级到这个包,问题也就解决了。

原来,.net core 在 build 时只检查 nuget 包的版本号,不检查包中 dll 程序集文件的版本号,而 .net core runtime 在运行时会检查程序集文件的版本号。而我们遇到的问题就是由于 build 时与运行时的检查机制不一致,造成错误的文件版本号没有在 build 时被检查出来,从而在应用运行的时候才发现,大大增加了问题的排查难度。

为什么一定要在运行时检查程序集文件的版本号而且在版本号出现问题时让整个应用都无法启动?反正就这一个文件,写个告警日志,继续用这个文件就是了,如果无法使用这个文件,再让整个应用挂掉也不迟,这个地方有点想不通。

今天的发现也让之前的另外一个疑问有了答案,之前在 .NET Core 2.2 中遇到了 System.Data.SqlClient 的问题,想看看最新版的 corefx 是否解决了这个问题,于是签出最新版的 corefx 代码 build 出了 System.Data.SqlClient.dll 替换 .NET Core 2.2 中的同名文件,运行时也总是报错 “Could not load file or assembly” ,只有签出 release/2.2 分支的代码 build 才行,原来也是文件版本号的问题,只要修改一下文件版本号就能解决。

另外,程序集的依赖信息保存在 .deps.json 文件中,如果不能修改程序集文件的版本号,应该可以通过修改 .deps.json 文件中 runtime 配置的 fileVersion 值来实现。

"runtime": {
  "lib/netstandard2.0/EnyimMemcachedCore.dll": {
    "assemblyVersion": "2.2.2.0",
    "fileVersion": "2.2.2.0"
  }
}

作者:dudu