NServiceBus.Newtonsoft.Json - Compatibility between message assembly version and framework versions

So we have a shared project between all endpoints called “Messages” with just our message classes (IEvent/ICommand).

We use GitVersionTask to stamp all assemblies we build with the version of the git tag in our CI build server.

Often, we want to do a hotfix of a single endpoint without impacting the others, so we upgrade the minor version and deploy only this endpoint (let’s say 3.97.3) while letting the other endpoints run in the older version (3.97.2). The build process update the version of that assembly dll containing all messages as well as the endpoint executable, but the content of the messages (classes/properties) stay the same.

We never had any issue doing that, until today. Seems like .NET framework endpoints can’t read messages from .NET5 endpoints if the version is not the same.

In short:
.NET Framework to .NET Framework with different assembly version -> works
.NET5 to .NET5 with different assembly version -> works
.NET5 to .NET Framework with same assembly version -> works
.NET5 to .NET Framework with different assembly version -> Error handling message

ServicePulse get filled with this exception (thousands of messages failing as fast as they come, and by the way the ServiceControl process ram usage rise drastically in this situation):

NServiceBus.MessageDeserializationException: An error occurred while attempting to extract logical messages from incoming physical message 4e58522d-3424-4576-a338-aca9013f354b ---> System.IO.FileLoadException: Could not load file or assembly 'Server.Messages, Version=3.97.3.0, Culture=neutral, PublicKeyToken=441f208f8bdad6b7' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName, ObjectHandleOnStack type)
   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName)
   at System.Type.GetType(String typeName, Boolean throwOnError)
   at NServiceBus.Unicast.Messages.MessageMetadataRegistry.GetType(String messageTypeIdentifier) in /_/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs:line 103
   at NServiceBus.Unicast.Messages.MessageMetadataRegistry.GetMessageMetadata(String messageTypeIdentifier) in /_/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs:line 49
   at NServiceBus.DeserializeMessageConnector.Extract(IncomingMessage physicalMessage) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 94
   at NServiceBus.DeserializeMessageConnector.ExtractWithExceptionHandling(IncomingMessage message) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 47
   --- End of inner exception stack trace ---
   at NServiceBus.DeserializeMessageConnector.ExtractWithExceptionHandling(IncomingMessage message) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 51
   at NServiceBus.DeserializeMessageConnector.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 31
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ReceivePerformanceDiagnosticsBehavior.<Invoke>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.ProcessingStatisticsBehavior.<Invoke>d__0.MoveNext() in /_/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs:line 25
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.TransportReceiveToPhysicalMessageConnector.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs:line 39
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.MainPipelineExecutor.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs:line 45
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at NServiceBus.Transport.RabbitMQ.MessagePump.<Process>d__29.MoveNext() in /_/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs:line 266

have you tried without the strong name?

Thank you Simon!

I did not realize the assembly was signed with a strong name. Without it the issue seems to go away. This is exactly the answer I was looking for!

Unfortunately, this trigger a scaffolding of other assemblies referencing it also needing to remove the strong name, but I think this is just a leftover of the past and I can safely remove it globally from my project now.

Other options