.NET 5项目dotnet publish后Newtonsoft.Json加载失败问题排查
在.NET 5环境下开发项目时遇到Newtonsoft.Json程序集加载异常,具体场景为:将应用打包为Docker容器运行时,Newtonsoft.Json库抛出如下错误:
System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified
项目依赖结构
- 类库
MyLibrary.A显式引用Newtonsoft.Json 13.0.1实现JSON序列化/反序列化功能 - 类库
MyLibrary.B封装了MassTransit.AmazonSQS库,该MassTransit库默认依赖Newtonsoft.Json 11.0.2,若在MyLibrary.B中显式引用Newtonsoft.Json 13.0.1,MassTransit可正常适配该高版本 - 上层Web应用
MyApp同时依赖MyLibrary.A和MyLibrary.B,本地作为Kestrel AspNetCore应用运行正常,但通过CI/CD生成Docker镜像后,本地用Docker Compose启动容器时抛出如下错误:
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.
注:错误提示找不到的Newtonsoft.Json 13.0.0.0版本实际不存在,该库的版本是从12.0.3直接迭代到13.0.1。
更新1:统一版本方案无效
尝试全项目统一显式引用Newtonsoft.Json 13.0.1,甚至直接在Web应用项目中添加该版本引用,问题仍未解决。Docker运行的完整错误堆栈如下:
docker run -p 8080:80 \ > -e ASPNETCORE_ENVIRONMENT=Production \ > registry.gitlab.com/sample/foo-integration-service:latest Unable to find image 'registry.gitlab.com/sample/foo-integration-service:latest' locally latest: Pulling from sample/foo-integration-service 07aded7c29c6: Pull complete 97aff7269a5a: Pull complete 633b89d569a5: Pull complete bd0e639a2ac9: Pull complete a9a5571a369e: Pull complete 9569d825ee3a: Pull complete Digest: sha256:5499b40392512f1731890ccf1ee13507769b733ee2f30c95d281f0550f7a892e Status: Downloaded newer image for registry.gitlab.com/sample/foo-integration-service:latest Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified. File name: 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) at foo.ItgService.Program.Main(String[] args) in /builds/sample/foo-integration-service/src/foo.ItgService/Program.cs:line 10
更新2:匹配MassTransit依赖版本仍无效
将全项目的Newtonsoft.Json版本统一为MassTransit依赖的11.0.2,错误变为:
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.
所有依赖已统一为11.0.2仍报错,将该版本显式添加到Web应用主程序集依赖中,问题依旧。
更新3:添加.dockerignore后仍未解决
添加了.dockerignore文件,内容如下:
bin/ obj/
使用GitLab+Kaniko构建镜像,流程为先执行dotnet build、dotnet publish,再将publish目录内容复制到Docker镜像中,Dockerfile内容如下:
FROM mcr.microsoft.com/dotnet/aspnet:5.0 COPY publish/ . EXPOSE 80 ENTRYPOINT ["dotnet", "MyCompany.ItgService.dll"]
对应的.gitlab-ci.yml配置如下:
image: mcr.microsoft.com/dotnet/sdk:5.0 variables: GIT_DEPTH: 1000 PUBLISH_OUTPUT_DIR: publish ENTRYPOINT_DLL: ReplaceMe.dll CLUSTER_NAME: ReplaceMe SERVICE_NAME: ReplaceMe stages: - build - test - publish - delivery build: stage: build script: - dotnet restore --no-cache --force - dotnet build --configuration Release --no-restore artifacts: paths: - test expire_in: 8 hour rules: - if: $CI_COMMIT_TAG when: never - when: always test: stage: test services: - name: localstack/localstack:0.12.17.5 alias: localstack variables: # Localstack with SNS and SQS AWS_DEFAULT_REGION: "us-east-1" EDGE_PORT: "4566" SERVICES: "sns,sqs" before_script: - rounds=10; while [ $rounds -gt 0 ]; do curl http://localstack:4566 && echo OK && break || echo FAIL rounds=$rounds - 1; sleep 5; done; script: dotnet test --blame --configuration Release rules: - if: $CI_COMMIT_TAG when: never - exists: - test/**/*Tests.csproj publish: stage: publish before_script: - export PATH=$PATH:/root/.dotnet/tools - dotnet tool install --global GitVersion.Tool --version 5.7.0 - dotnet gitversion - SEMVER=$(dotnet gitversion -showvariable semver) - mkdir version - echo "${SEMVER}" > ./version/semver - APP_VERSION=$(cat ./version/semver) script: - dotnet publish -c Release -o $PUBLISH_OUTPUT_DIR -p:Version=$APP_VERSION artifacts: paths: - $PUBLISH_OUTPUT_DIR/ - version/ expire_in: 8 hour rules: - if: $CI_COMMIT_TAG when: never - when: always container_registry: stage: delivery image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] before_script: - IMAGE_TAG=$(cat ./version/semver) - echo "bin/" > $CI_PROJECT_DIR/.dockerignore - echo "obj/" > $CI_PROJECT_DIR/.dockerignore - echo "FROM mcr.microsoft.com/dotnet/aspnet:5.0" > $CI_PROJECT_DIR/Dockerfile - echo "COPY $PUBLISH_OUTPUT_DIR/ ." >> $CI_PROJECT_DIR/Dockerfile - echo "EXPOSE 80" >> $CI_PROJECT_DIR/Dockerfile - echo "ENTRYPOINT [\"dotnet\", \"$ENTRYPOINT_DLL\"]" >> $CI_PROJECT_DIR/Dockerfile - cat $CI_PROJECT_DIR/Dockerfile - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - cat /kaniko/.docker/config.json script: - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:latest --destination $CI_REGISTRY_IMAGE:$IMAGE_TAG rules: - if: $CI_COMMIT_TAG when: never - when: always
已确认dotnet publish生成的目录中存在Newtonsoft.Json.dll,且.deps.json中所有Newtonsoft.Json引用均为11.0.2,无版本冲突,该构建流程对其他服务均正常。
更新4:清理NuGet缓存未解决
清理了本地所有NuGet缓存,命令如下:
dotnet nuget locals all --clear Clearing NuGet HTTP cache: /home/diegosasw/.local/share/NuGet/v3-cache Clearing NuGet global packages folder: /home/diegosasw/.nuget/packages/ Clearing NuGet Temp cache: /tmp/NuGetScratch Clearing NuGet plugins cache: /home/diegosasw/.local/share/NuGet/plugins-cache Local resources cleared.
恢复依赖后发现NuGet缓存中存在Newtonsoft.Json 9.0.1版本,后续排查确认该版本是测试项目的依赖,来自coverlet.collector,不影响主程序。通过docker export命令也验证了容器内确实存在Newtonsoft.Json.dll文件。
更新6:确认问题与Docker无关,出在dotnet publish环节
排除Docker影响,单独执行dotnet publish -c Release -o publish后直接运行publish目录的程序可复现异常,但执行dotnet run -c Release --project src/Rubiko.ItgService运行正常。
核心疑问
- 为什么dotnet publish没有生成应用运行所需的全部依赖?
- 为什么运行时会提示已存在的程序集找不到?
$ ls publish/ | grep Newtonsoft Newtonsoft.Json.Bson.dll* Newtonsoft.Json.dll*
最终结论
问题已解决,根因是使用了不同版本Newtonsoft.Json的测试项目也发布了产物,覆盖了正确的依赖程序集,确保测试项目不发布产物即可解决。
内容的提问来源于stack exchange,提问作者diegosasw

