This sample illustrates an approach for changing a message type. This includes any one, or multiple, of the following scenarios:
- Moving a message type between assemblies.
- Renaming a message type.
- Renaming the assembly containing the message type.
- Adding, removing or changing the strong name of the assembly containing the message type
This sample uses 2 "Phase" Endpoint Projects to illustrate the iterations of a single endpoint in one solution.
Scenario
This sample shows moving a message using multiple of the above mentioned scenarios.
Phase 1
In the Phase 1 endpoint the message type is:
- Named
CreateOrderPhase1
. - Exists in an assembly named
SamplePhase1
. - Exists in an assembly that is not strong named.
Phase 2
In the Phase 2 endpoint the message type is:
- Named
CreateOrderPhase2
. - Exists in an assembly named
SamplePhase2
. - Exists in an assembly that is strong named.
Mutation
This change is achieved via the use of a IMutateIncomingTransportMessages.
The mutator is registered at endpoint startup:
endpointConfiguration.RegisterMessageMutator(new MessageIdentityMutator());
The mutator then replaces the NServiceBus.EnclosedMessageTypes header via the use of Type.GetType(typeName, assemblyResolver, typeResolver) API.
class MessageIdentityMutator :
IMutateIncomingTransportMessages
{
public Task MutateIncoming(MutateIncomingTransportMessageContext context)
{
var headers = context.Headers;
var messageTypeKey = "NServiceBus.EnclosedMessageTypes";
if (!headers.TryGetValue(messageTypeKey, out var messageType))
{
return Task.CompletedTask;
}
var type = Type.GetType(
typeName: messageType,
assemblyResolver: assemblyName =>
{
if (assemblyName.Name == "SamplePhase1")
{
Console.WriteLine("Message received from SamplePhase1 assembly, changing to SamplePhase2 assembly");
return AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("SamplePhase2"));
}
return AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
},
typeResolver: (assembly, typeName, ignoreCase) =>
{
if (typeName == "CreateOrderPhase1")
{
Console.WriteLine("CreateOrderPhase1 received, changing to CreateOrderPhase2");
return typeof(CreateOrderPhase2);
}
if (assembly != null)
{
return assembly.GetType(typeName);
}
return Type.GetType(typeName);
},
throwOnError: true);
if (type == null)
{
throw new Exception($"Could not determine type: {messageType}");
}
headers[messageTypeKey] = type.AssemblyQualifiedName;
return Task.CompletedTask;
}
}
Running the sample
When the sample is run both endpoints will startup. The Phase1 endpoint will send a CreateOrderPhase1
message to Phase2. Phase2 will then mutate the message into a CreateOrderPhase2
and handle the message.