ymerej ymerej - 2 months ago 25
C# Question

HelloWorld example for sending an object over RabbitMQ via EasyNetQ between two different applications

Hi I am attempting to send a simple object like through RabbitMQ via EasyNetQ. I'm having issues deserializing that object on the subscription side. Anyone able to show me a sample of how this works. Keep in mind the object being sent is defined in it's own project and not shared among the publisher and subscriber. Here is my sample, and perhaps you can tell me what is wrong with it?

Program A:

class ProgramA
{
static void Main(string[] args)
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
Console.WriteLine("Press any key to send the message");
Console.ReadKey();
bus.Publish(new MessageA { Text = "Hello World" });
Console.WriteLine("Press any key to quit");
Console.ReadKey();
}
}

public class MessageA
{
public string Text { get; set; }
}
}


Program B:

class ProgramB
{
static void Main(string[] args)
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
bus.Subscribe<MessageB>("", HandleClusterNodes);
Console.WriteLine("Press any key to quit");
Console.ReadKey();
}
}

private static void HandleClusterNodes(MessageB obj)
{
Console.WriteLine(obj.Text);
}

[Queue("TestMessagesQueue", ExchangeName = "EasyNetQSample.ProgramA+MessageA:EasyNetQSample")]
public class MessageB
{
public string Text { get; set; }
}
}


Here is the error I'm receiving:

DEBUG: HandleBasicDeliver on consumer: f9ded52d-039c-411a-9b9f-5c8ee3301854, deliveryTag: 1
DEBUG: Received
RoutingKey: ''
CorrelationId: 'ec41faea-a0c8-4ffd-8163-2cbf85d45fcd'
ConsumerTag: 'f9ded52d-039c-411a-9b9f-5c8ee3301854'
DeliveryTag: 1
Redelivered: False
ERROR: Exception thrown by subscription callback.
Exchange: 'EasyNetQSample.ProgramA+MessageA:EasyNetQSample'
Routing Key: ''
Redelivered: 'False'
Message:
{"Text":"Hello World"}
BasicProperties:
ContentType=NULL, ContentEncoding=NULL, Headers=[], DeliveryMode=2, Priority=0, CorrelationId=ec41faea-a0c8-4ffd-8163-2cbf85d45fcd, ReplyTo=NULL, Expiration=NULL, MessageId=NULL, Timestamp=0, Type=EasyNetQSample.ProgramA+MessageA:EasyNetQSample, UserId=NULL, AppId=NULL, ClusterId=NULL
Exception:
System.AggregateException: One or more errors occurred. ---> EasyNetQ.EasyNetQException: Cannot find type EasyNetQSample.ProgramA+MessageA:EasyNetQSample
at EasyNetQ.TypeNameSerializer.DeSerialize(String typeName)
at EasyNetQ.DefaultMessageSerializationStrategy.DeserializeMessage(MessageProperties properties, Byte[] body)
at EasyNetQ.RabbitAdvancedBus.<>c__DisplayClass19.<Consume>b__18(Byte[] body, MessageProperties properties, MessageReceivedInfo messageReceivedInfo)
at EasyNetQ.RabbitAdvancedBus.<>c__DisplayClass1e.<Consume>b__1d(Byte[] body, MessageProperties properties, MessageReceivedInfo receviedInfo)
at EasyNetQ.Consumer.HandlerRunner.InvokeUserMessageHandler(ConsumerExecutionContext context)
--- End of inner exception stack trace ---
---> (Inner Exception #0) EasyNetQ.EasyNetQException: Cannot find type EasyNetQSample.ProgramA+MessageA:EasyNetQSample
at EasyNetQ.TypeNameSerializer.DeSerialize(String typeName)
at EasyNetQ.DefaultMessageSerializationStrategy.DeserializeMessage(MessageProperties properties, Byte[] body)
at EasyNetQ.RabbitAdvancedBus.<>c__DisplayClass19.<Consume>b__18(Byte[] body, MessageProperties properties, MessageReceivedInfo messageReceivedInfo)
at EasyNetQ.RabbitAdvancedBus.<>c__DisplayClass1e.<Consume>b__1d(Byte[] body, MessageProperties properties, MessageReceivedInfo receviedInfo)
at EasyNetQ.Consumer.HandlerRunner.InvokeUserMessageHandler(ConsumerExecutionContext context)<---


What do I need to do to be able to properly deserialize
MessageA
?

Answer

As far as I know, the default setting of the EasyNetQ requires the type of the serialized object to be consistent between applications. For example, you can send any known .NET type easily like String:

 bus.Publish<String>("Excellent.");

and it will be happy on both projects.

You can use your own Message if you put it to a common library (dll). Since you specially mentioned that they reside in different projects, I'd suggest to serialize and cast objects yourself.

EasyNetQ uses interally Newtonsoft Json.NET to serialize objects like this. As you can see, your message has been serialized already as:

Message: {"Text":"Hello World"}

To do this yourself, you still need to add a reference to Json.NET because EasyNetQ hides this reference by using ilrepack.

This should work:

bus.Publish<string>(JsonConvert.SerializeObject(new MessageA { Text = "Hello World" }));

and

bus.Subscribe<string>("", HandleClusterNodes);

private static void HandleClusterNodes(string obj)
{
    var myMessage = (MessageB)JsonConvert.DeserializeObject<MessageB>(obj);
    Console.WriteLine(myMessage.Text);
}

But you'll lose your attribute based routing with this and might want to modify your methods.

If you want to keep using basic methods, you can set topic with like this:

bus.Publish<string>(JsonConvert.SerializeObject(new MessageA { Text = "Hello World" }), "topic.name");

bus.Subscribe<string>("", HandleClusterNodes, new Action<EasyNetQ.FluentConfiguration.ISubscriptionConfiguration>( o => o.WithTopic("topic.name")));

But to have complete control, you need to use Advanced API;

var yourMessage = new Message<string>(JsonConvert.SerializeObject(new MessageA { Text = "Hello World" }));
bus.Advanced.Publish<string>(new Exchange("YourExchangeName"), "your.routing.key", false, false, yourMessage);

and on the subscriber part:

IQueue yourQueue = bus.Advanced.QueueDeclare("AnotherTestMessagesQueue");
IExchange yourExchange = bus.Advanced.ExchangeDeclare("YourExchangeName", ExchangeType.Topic);
bus.Advanced.Bind(yourExchange, yourQueue, "your.routing.key");
bus.Advanced.Consume<string>(yourQueue, (msg, info) => HandleClusterNodes(msg.Body));

which is the almost same as the original RabbitMQ C# Client API.


Detailed analysis:

The main problem is this exception:

EasyNetQ.EasyNetQException: Cannot find type EasyNetQSample.ProgramA+MessageA:EasyNetQSample

This is thrown by EasyNetQ because it cannot find the special class on the endpoint.

If we look at source code of the TypeNameSerializer.cs, you will see

var type = Type.GetType(nameParts[0] + ", " + nameParts[1]);
            if (type == null)
            {
                throw new EasyNetQException(
                    "Cannot find type {0}",
                    typeName);
            }

this is where it tried to find EasyNetQSample.ProgramA.MessageA type on second project, while it only knows EasyNetQSample.ProgramB.MessageB.

Alternatively, you can roll out your own custom ISerializer or put an ITypeNameSerializer into the default serializer but I haven't tried this.