How to convert RabbitMQ Messages into object list in c# - c#

I am publishing json messages into a queue in rabbitmq and it is workin properly. But facing an issue that i want to consume all data in the issued queue (as a chat app) and i have to use all messages.
For example I have 9 items in the queue as below
{"Sender":123,"Message":"Test Message-1","Group":1}
{"Sender":123,"Message":"Test Message-2","Group":1}
{"Sender":123,"Message":"Test Message-3","Group":1}
{"Sender":123,"Message":"Test Message-4","Group":1}
{"Sender":567,"Message":"Test Message-5","Group":21}
{"Sender":123,"Message":"Test Message-6","Group":1}
{"Sender":456,"Message":"Test Message-7","Group":1}
{"Sender":456,"Message":"Test Message-8","Group":1}
{"Sender":123,"Message":"Test Message-9","Group":1}
These all messages are stored in queue as i want. But when i try to collect them with an api call as below it won't work properly. Sometimes getting data but sometimes don't get any data and acked the list. So is there any way to get all or limited data into an object or array in c#. Because all examples are consuming messages into the Console. I need to get as a collection.
public IList<string> GetMessageFromQueue(string _key, bool AutoAck = false)
{
var _list = new List<string>();
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: _key,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var response = channel.QueueDeclarePassive(_key);
var _test= response.MessageCount;
var _test2 = response.ConsumerCount;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
_list.Add(message);
};
//if (_list.Count == 0)
// AutoAck = false;
channel.BasicConsume(queue: _key,
autoAck: AutoAck,
consumer: consumer);
}
return _list;
}
And My Controller
public IActionResult Collect(){
_queueClient.GetMessageFromQueue("myKey",true);
}
This method olsa clears the queue because of BasicConsume's autoack property. I tried to use basicAck also.
What is the best way to get messages to an object array for next operations in rabbitmq/c#.

It seems to me that your function GetMessageFromQueue is going through the motions of setting everything up, but then exiting from the function straight away without waiting for your Received function to collect all the messages.
So for example, this the the inline function you setup to collect the messages from the queue:
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
_list.Add(message);
};
...but then 2 lines later you simply exit the function straight away without waiting for your Recieved function to added all the messages to your list.
// exit function straight away!
return _list;
I notice in your sample code, that you can get a count of the messages held on the queue. This is good because it means you know how many messages to expect to recieve.
var _test= response.MessageCount;
So, one thing you could try and do is add a ManualResetEventSlim or SemaphoreSlim to wait at the bottom of your function until its signaled and then return (there might be better ways of doing this but thats the idea that popped in my head right now)
For example, at the top of the fucntion create a ManualResetEventSlim event
var msgsRecievedGate = new ManualResetEventSlim(false);
and then before exiting the function wait for it to be set.
msgsRecievedGate.Wait();
Something like this:
public IList<string> GetMessageFromQueue(string _key, bool AutoAck = false)
{
var _list = new List<string>();
// Setup synchronization event.
var msgsRecievedGate = new ManualResetEventSlim(false);
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: _key,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var response = channel.QueueDeclarePassive(_key);
var msgCount = response.MessageCount;
var msgRecieved = 0;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
msgRecieved++;
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
_list.Add(message);
if ( msgRecieved == msgCount )
{
// Set signal here
msgsRecievedGate.Set();
// exit function
return;
}
};
channel.BasicConsume(queue: _key,
autoAck: AutoAck,
consumer: consumer);
}
// Wait here until all messages are retrieved
msgsRecievedGate.Wait();
// now exit function!
return _list;
}
Please beware. I have not tested the above code so your mileage my vary.

Related

RabbitMQ: How multiple consumers can receive messages from the same queue?

I run the producer, it generates N messages, i see them on the dashboard.
When I run a receiver it receive all messages from the queue and the queue is an empty.
static void Receive(string QueName)
{
ConnectionFactory connectionFactory = new ConnectionFactory
{
HostName = HostName,
UserName = UserName,
Password = Password,
};
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
channel.BasicQos(0, 1, false);
MessageReceiver messageReceiver = new MessageReceiver(channel);
channel.BasicConsume(QueName, false, messageReceiver);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
// Receiver
public class MessageReceiver : DefaultBasicConsumer
{
private readonly IModel _channel;
public MessageReceiver(IModel channel)
{
_channel = channel;
}
public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties properties, ReadOnlyMemory<byte> body)
{
Console.WriteLine($"------------------------------");
Console.WriteLine($"Consuming Message");
Console.WriteLine(string.Concat("Message received from the exchange ", exchange));
Console.WriteLine(string.Concat("Consumer tag: ", consumerTag));
Console.WriteLine(string.Concat("Delivery tag: ", deliveryTag));
Console.WriteLine(string.Concat("Routing tag: ", routingKey));
//Console.WriteLine(string.Concat("Message: ", Encoding.UTF8.GetString(body)));
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine(string.Concat("Message: ", message));
Console.WriteLine($"------------------------------");
_channel.BasicAck(deliveryTag, false);
}
}
I need to have multiple producers which generate messages to the same queue.
And multiple customers receive messages from the queue. And messages will be deleted by queue TTL.
But now the 1st receiver gets all messages from the queue.
How can I do this?
The best solution is : every client should have its own queue, may be with TTL, may be with expiration parameter.
We use “exchange” here, just to show the exchange mechanics in same sample, it’s not really needed for the task (check Worker2 project, it works with another queue, which is binded to the same exchange):
channel.ExchangeDeclare(exchange: “logs”, type: ExchangeType.Fanout);
Full sample of consumption
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Timers;
class Worker
{
public static void Main()
{
// Test of timer handler
System.Timers.TimeraTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler((source, e)
=> Console.Write("Timer Test"));
aTimer.Interval=3000;
// Test timer
// aTimer.Enabled = true;
var factory = new ConnectionFactory()
{
HostName = "localhost", UserName="user", Password="password",
// DispatchConsumersAsync = true
};
var connection = factory.CreateConnection();
// Add multiple consumers, so that queue can be processed "in
// parallel"
for (int i=1; i<10; i++)
{
var j=i;
var channel = connection.CreateModel();
channel.ExchangeDeclare(exchange: "logs", type:
ExchangeType.Fanout);
var queueName=channel.QueueDeclare("test1", durable: true,
autoDelete: false, exclusive: false);
// take 1 message per consumer
channel.BasicQos(0, 1, false);
channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: "");
Console.WriteLine($" [*] Waiting for messages in {j}");
var consumer = new EventingBasicConsumer(channel);
consumer. Received+= (model, ea) =>
{
byte[] body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] Received in {j} -> {message} at
{DateTime.Now}");
// Thread.Sleep(dots * 1000);
// await Task.Delay(3000);
Thread.Sleep(10000);
// async works too
if (j==5)
{
// Test special case of returning item to queue: in
// this case we received the message, but did not process
// it because of some reason.
// QOS is 1, so our consumer is already full. We need
// to return the message to the queue, so that another
// consumer can work with it
Console.WriteLine($"[-] CANT PROCESS {j} consumer!
Error with -> {message}");
channel.BasicNack(deliveryTag: ea.DeliveryTag,
multiple: false, true);
}
else
{
Console.WriteLine($" [x] Done {j} -> {message} at
{DateTime.Now}");
// here channel could also be accessed as
((EventingBasicConsumer)sender).Model
channel.BasicAck(deliveryTag: ea.DeliveryTag,
multiple: false);
}
};
channel.BasicConsume(queue: queueName, autoAck: false,
consumer: consumer);
}
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
full example in link

Passing ClientProperties with "consumer_cancel_notify" set to "false" in rabbitmq-dotnet-client

I use async rabbitmq consumer based on rabbitmq-dotnet-client.
Here is a simplify code
using (_channel = RabbitMqConnectionFactory.Connection.CreateModel())
{
_channel.QueueDeclare(Constants.QueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
_channel.BasicQos(0, 1, false);
var consumer = new AsyncEventingBasicConsumer(_channel);
consumer.Received += async (o, a) =>
{
await HandleMessageEvent(o, a);
};
string tag = _channel.BasicConsume(Constants.QueueName, false, consumer);
while (IsWorking)
{
await Task.Delay(6000);
}
_channel.BasicCancel(tag);
IsWorking = false;
}
I want RabbitMQ server not to send an ACK in response to a BasicCancel message.
According to the documentation , I can pass the parameter consumer_cancel_notify with the false value in the ClientProperties property of the connection when it's established.
I try to do so with such code.
public static ConnectionFactory GetRabbitMqConnectionFactory()
{
Dictionary<string, bool> capabilities = new Dictionary<string, bool>
{
["consumer_cancel_notify"] = false
};
var result = new ConnectionFactory
{
ContinuationTimeout = TimeSpan.FromSeconds(5),
HostName = "localhost",
UserName = "guest",
Password = "guest",
DispatchConsumersAsync = true,
ClientProperties =
{
["capabilities"] = capabilities
},
};
return result;
}
But, this does not work, since the server still sends the ACK to the BasicCancel message, which i can handle with ConsumerCancelled AsyncEventHandled.
I use RabbitMQ Server version 3.8.3 and rabbitmq-dotnet-client version 5.1.2.
How can i pass the consumer_cancel_notify parameter to the RabbitMQ broker?
In version 6.0.0 of the RabbitMQ .NET client, a method void BasicCancelNoWait (string consumerTag) has been added to the public API.
When using this method on the client side, the server does not send the ACK in response to a BasicCancel request.

RabbitMQ not receiving messages when used with TopShelf as a Windows Service

I'm trying to convert my RabbitMQ micro-service to a windows service. I have used TopShelf for the conversion. My RabbitMQ micro-service works perfectly fine on its own but when I run it as a service it no longer receives messages. In my public static void Main(string[] args) I have:
HostFactory.Run(host =>
{
host.Service<PersonService>(s =>
{
s.ConstructUsing(name => new PersonService());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
host.SetDescription("Windows service that provides database access totables.");
host.SetDisplayName("Service");
host.SetServiceName("Service");
});
}
Then in my PersonService class I have
public void Start() {
ConsumeMessage();
}
And finally my ConsumeMessage function:
private static void ConsumeMessage() {
MessagingConfig.SetInstance(new MessagingConstants());
IMessageFactory pmfInst = MessageFactory.Instance;
//message worker
var factory = new ConnectionFactory() {
HostName = MessagingConfig.Instance.GetBrokerHostName(),
UserName = MessagingConfig.Instance.GetBrokerUserName(),
Password = MessagingConfig.Instance.GetBrokerPassword()
};
var connection = factory.CreateConnection();
using (var channel = connection.CreateModel()) {
channel.QueueDeclare(queue: MessagingConfig.Instance.GetServiceQueueName(),
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.BasicQos(0, 1, false);
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: MessagingConfig.Instance.GetServiceQueueName(),
noAck: false,
consumer: consumer);
Console.WriteLine("Service.");
Console.WriteLine(" [x] Awaiting RPC requests");
// Code Below Is Not Executed In Service
consumer.Received += (model, ea) => {
string response = null;
var body = ea.Body;
var props = ea.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelationId = props.CorrelationId;
string receivedMessage = null;
try {
receivedMessage = Encoding.UTF8.GetString(body);
response = ProcessMessage(receivedMessage);
}
catch (Exception e) {
// Received message is not valid.
WinLogger.Log.Error(
"Errror Processing Message: " + receivedMessage + " :" + e.Message);
response = "";
}
finally {
var responseBytes = Encoding.UTF8.GetBytes(response);
channel.BasicPublish(exchange: "", routingKey: props.ReplyTo,
basicProperties: replyProps, body: responseBytes);
channel.BasicAck(deliveryTag: ea.DeliveryTag,
multiple: false);
}
};
Console.ReadLine();
}
Looking at A similar SO question it looks like it has something to do with the return the Windows Service is wanting, but I'm not sure of how to call ConsumeMessage so consumer.Received += (model, ea) => {...}; is executed.
EDIT: It looks like my blocking mechanism Console.ReadLine(); is ignored by the service so it just continues on and disposes of the message consumer. So how do I block there for messages to be received?
You code uses using construct, which means when your OnStart method returns, your channel will actually be disposed. The docs suggest to do your initialization on OnStart, so create your channel and consumer there, but don't use using:
this.connection = factory.CreateConnection();
this.channel = connection.CreateModel();
this.consumer = new EventingBasicConsumer(this.channel);
Then those objects will continue to exist after OnStart method is finished. You should dispose of them in the OnStop method.

C#\RabbitMq : method refactoring breaks functionality?

I am learning RabbitMq with .NET. According to the tutorial, simplest implementation of consumer looks like :
public class Receive
{
public static void Main()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "hello",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
};
channel.BasicConsume(queue: "hello",
noAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}
And it works correctly. However, I wanted to refactor it: let define functionality of receiver in separate method. It looks like :
public class Recieve
{
private ConnectionFactory factory;
public void ConsumeSimpleMessage(string queueName = "default")
{
using(var connection = factory.CreateConnection())
{
using(var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: queueName, durable: false,
exclusive: false, autoDelete: false, arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(message);
};
channel.BasicConsume(queue: queueName,
noAck: true,
consumer: consumer);
}
}
}
public Recieve(string hostName = "localhost")
{
factory = new ConnectionFactory() { HostName = hostName };
}
}
And when I try to call this method in Main():
class Program
{
static void Main(string[] args)
{
Recieve reciever = new Recieve();
reciever.ConsumeSimpleMessage();
Console.ReadLine();
}
}
here it doesn't work. It show nothing. However, messages will be deleted, meaning that they were recieved. Why does it happen? Is there anything I didn't know about Event Handing?
Try to see if it works without doing using, Or if you want to use the using statement keep the Console.Read() inside the using statement and see if that works. You can keep the connection and channel open and don't have to close it manually.
If you really want to debug then you can put the breakpoint on the consumer.Receive and see if you can see the message staying unacked. That way you know when the message will be deleted.
Also I usually recommend using tracer for rabbitmq as it logs all the messages coming in the server which makes it easy to trace.

RabbitMQ C# verify message was sent

I'm new to RabbitMQ and trying to write to a Queue and verify the message was sent. If it fails I need to know about it.
I made a fake queue to watch it fail but no matter what I see no execptions and when I am looking for a ack I always get one. I never see the BasicNack.
I'm not even sure i'm the BasicAcks is the way to go.
private void button1_Click(object sender, EventArgs e)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("task_queue", true, false, false, null);
var message = ("Helllo world");
var body = Encoding.UTF8.GetBytes(message);
channel.ConfirmSelect();
var properties = channel.CreateBasicProperties();
properties.SetPersistent(true);
properties.DeliveryMode = 2;
channel.BasicAcks += channel_BasicAcks;
channel.BasicNacks += channel_BasicNacks;
//fake queue should be task_queue
channel.BasicPublish("", "task_2queue", true, properties, body);
channel.WaitForConfirmsOrDie();
Console.WriteLine(" [x] Sent {0}", message);
}
}
}
void channel_BasicNacks(IModel model, BasicNackEventArgs args)
{
}
void channel_BasicAcks(IModel model, BasicAckEventArgs args)
{
}
For those looking for a C# answer - here is what you need.
https://rianjs.net/2013/12/publisher-confirms-with-rabbitmq-and-c-sharp
Something like this: (BasicAcks attaches an event handler - there is also BasicNacks)
using (var connection = FACTORY.CreateConnection())
{
var channel = connection.CreateModel();
channel.ExchangeDeclare(QUEUE_NAME, ExchangeType.Fanout, true);
channel.QueueDeclare(QUEUE_NAME, true, false, false, null);
channel.QueueBind(QUEUE_NAME, QUEUE_NAME, String.Empty, new Dictionary<string, object>());
channel.BasicAcks += (sender, eventArgs) =>
{
//implement ack handle
};
channel.ConfirmSelect();
for (var i = 1; i <= numberOfMessages; i++)
{
var messageProperties = channel.CreateBasicProperties();
messageProperties.SetPersistent(true);
var message = String.Format("{0}\thello world", i);
var payload = Encoding.Unicode.GetBytes(message);
Console.WriteLine("Sending message: " + message);
channel.BasicPublish(QUEUE_NAME, QUEUE_NAME, messageProperties, payload);
channel.WaitForConfirmsOrDie();
}
}
You need a Publisher Confirms
as you can read you can implement:
The transaction:
ch.txSelect(); <-- start transaction
ch.basicPublish("", QUEUE_NAME,
MessageProperties.PERSISTENT_BASIC,
"nop".getBytes());
ch.txCommit();<--commit transaction
The message is stored to the queue and to the disk.
This way can be slow, if you need performance you shouldn't use it.
You can use the Streaming Lightweight Publisher Confirms, using:
ch.setConfirmListener(new ConfirmListener() {
public void handleAck(long seqNo, boolean multiple) {
if (multiple) {
unconfirmedSet.headSet(seqNo+1).clear();
} else {
unconfirmedSet.remove(seqNo);
}
}
public void handleNack(long seqNo, boolean multiple) {
// handle the lost messages somehow
}
I hope it helps
Ok, you always get the ACK for your message sent because "Every time message is delivered to Default Exchange Successfully."
PS: You are not sending message directly to Queue, Once Exchange recevis the message it gives you ACK then it route the message to all bound queue using the routing keys if any.

Categories

Resources