Sending Messages by rabbitMq - c#

I am using rabbit-Mq in my web app(Asp.net-MVC 4.0). My requirement is to send a message to a particular user. Suppose if user1 is online and he sends a message to user2 by rabbit-Mq. It should be received by "user2" only. The code I have used is a template which stores the message in the queue and whenever the user clicks on receive he will get that message but there is no restriction of particular user in my case. Anyone can get that message which is wrong and I have to handle that. Please help me regarding this.
Do we have something in rabbit-Mq that can distinguish the correct message to correct user/Consumer? Can we set a key with message and check the key while receiving?
Is this possible?
Below I am writing the code I am using to send and receive the messages
public ActionResult SendMessage(MessagingModel ObjModel)
{ var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
Message = ObjModel.Message;
channel.QueueDeclare("MessageQueue", true, false, false, null);
var body = Encoding.UTF8.GetBytes(ObjModel.Message);
channel.BasicPublish("", "MessageQueue", null, body);
}
}
}
public JsonResult RecieveMessage()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("MessageQueue", true, false, false, null);
bool noAck = true;
BasicGetResult result = channel.BasicGet("MessageQueue", noAck);
if (result == null)
{
Message = "No Messages Found.";
}
else
{
IBasicProperties props = result.BasicProperties;
byte[] Body = result.Body;
Message = Encoding.Default.GetString(Body);
}
}
}

First, you must remember the following things:
All messages in RabbitMQ published through exchanges.
Queues binded to exchanges.
Event if you publish message directly into queue, actually it still passes through the default exchange - (AMPQ default).
There are different kinds of exchanges. You can read a bit about exchanges here: https://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html
In you case you might consider use a topic or headers exchanges, but in this case you should for each user
have a queue, and if the number of users in the system is large, then it will be very resource intensive.
Also you can add specific header to you message:
var props = model.CreateBasicProperties();
props.Headers.Add("UserId", userId);
and then in RecieveMessage() method after read message from queue see this header and if message intended for current user - receive it and acknowledge this message, otherwise
not acknowledge this message.
But this is bad solution. I would just kept messages from the queue to the database, and then read them out filtering by user.

Related

Unable to get Topic Exchange to Work in RabbitMQ AMQP 1.0

I am finding it very difficult to work with RabbitMQ and AMQP 1.0, particularly as it pertains to topic exchanges using AMQPNetLite. I am unable to send a message using a topic exchange to a specific queue. I'm not even using wildcards.
My situation is super simple too. I have one topic exchange. I have one queue that the topic exchange sends to. When I send to the topic exchange, the queue never receives the message.
test.exchange:
bind: testqueue - routing key: test
testqueue:
bound to exchange with routing key: test
The AMQP 1.0 documentation says that the "Subject" is the routing key right? Well when I use AMQPNetLite to send to RabbitMQ, it appears to connect, and the topic appears to have received the message, but it is never routed to the queue.
Here's the entire code:
var rabbitMqAddress = $"amqp://127.0.0.1:5672";
var address = new Address(rabbitMqAddress);
var producerName = $"Producer-test.topic-{Time.GetTimeStamp()}";
var connection = new Connection(address, null, new Open
{
ContainerId = Guid.NewGuid().ToString(),
ChannelMax = 64,
}, null);
var session = new Session(connection);
var senderLink = new SenderLink(session, producerName, "/topic/test.exchange");
senderLink.Send(new Message
{
BodySection = new AmqpValue { Value = "test 123" },
Properties = new Properties
{
Subject = "test",
}
});
The image proves the binding. Is there something I'm missing?
i think you mix two ways of doing it.
Either you publish to address "/topic/test" -where test is your routingkey
OR
you publish to "/exchange/test.exchange" and set the Subject-property to "test".
Both works. if you use the "/topic/"-prefix in your address you are going through the default "amq.topic"-exchange and not your own "test-exchange".
made sense?
more information in the "Routing and Addressing"-secion here: https://github.com/rabbitmq/rabbitmq-amqp1.0

How to get the Service URL of a deployed bot application in Microsoft Bot framework?

I am using DirectLine API to send message to the bot, I need the service URL of the Published Bot to perform a post request for the load test as mentioned in the steps here https://blog.botframework.com/2017/06/19/Load-Testing-A-Bot/
This is the code, can anyone point where I am going wrong
private static async Task<Chat> TalkToTheBot(string Message)
{
Chat objChat = null;
// Connect to the DirectLine service
try
{
DirectLineClient client = new DirectLineClient(directLineSecret);
Conversation conversation = await client.Conversations.StartConversationAsync();
string watermark = null;
Activity reply = new Activity
{
From = new ChannelAccount("User1", "User Name"),
Text = "Hello",
Type = ActivityTypes.Message,
};
//await client.Conversations.PostActivityAsync(conversation.ConversationId, reply.CreateReply(text: Message, locale: "en-US"), CancellationToken.None);
await client.Conversations.PostActivityAsync(conversation.ConversationId,reply , CancellationToken.None);
// Get the response as a Chat object
objChat = await ReadBotMessagesAsync(client, conversation.ConversationId, watermark);
}
catch (Exception e)
{
throw;
}
// Return the response as a Chat object
return objChat;
}
private static async Task<Chat> ReadBotMessagesAsync(DirectLineClient client, string conversationId, string watermark)
{
// Create an Instance of the Chat object
Chat objChat = new Chat();
// We want to keep waiting until a message is received
bool messageReceived = false;
while (!messageReceived)
{
// Get any messages related to the conversation since the last watermark
ActivitySet messages = await client.Conversations.GetActivitiesAsync(conversationId, watermark, CancellationToken.None);
// Set the watermark to the message received
watermark = messages?.Watermark;
// Get all the messages
var messagesFromBotText = from message in messages.Activities
where message.From.Id == botId
select message;
// Loop through each message
foreach (var message in messagesFromBotText)
{
// We have Text
if (message.Text != null)
{
// Set the text response
// to the message text
objChat.ChatResponse
+= " "
+ message.Text.Replace("\n\n", "<br />");
}
}
// Mark messageReceived so we can break
// out of the loop
messageReceived = true;
}
// Set watermark on the Chat object that will be
// returned
objChat.watermark = watermark;
// Return a response as a Chat object
return objChat;
}
Per the article,
The serviceUrl property here is critical to note, and needs to be set
to the endpoint of your message sink/client.
and:
In order to test your bot, you’ll need to create a custom UI/message
sink to send and receive messages to your bot. This message sink will
effectively act like a channel and accept HTTP POST messages with
JSON-serialized bot framework activities.
Which basically means that you will have to build a "message client" and the url of that client is the one that you will have to provide in the serviceUrl of your request.

Microsoft DirectLine API to bot does not work

So to explain my problem, I have to give you the context.
I got a Bot built with microsoft bot framework deployed on slack. Now it can happen these "events" on my backend that the bot communicates with. When a event occurs, I want to notify my bot of it and then let it send a message to all of it's conversations that something has happend. So basicly:
Backend>Microserivce>Bot>users
To do this I have to store all conversations in my backend, which I do in a database there. When a event happends, the backend will post an activity to the bot with all the conversations(basicly their id's) and the event it should show them.
So in essence my backend need to post a message to my bot.
For doing this I found the microsoft directline api which acts as a middleman here, a more abstract way to talk with the bot. The problem is that I don't know how to do it. I followed microsofts own tutorial but it doesn't seem to work for me:
This is the endpoint that my backend uses to notify the bot. "content" contains conversations and events as a json formated string.
[HttpPost]
[Route("conversationsEvents")]
public HttpResponseMessage PostConversationsEvents([FromBody]string content)
{
NotifyBot.Notify(content);
return Request.CreateResponse(HttpStatusCode.NoContent );
}
NotifyBot.Notify(content) looks like this:
private static async Task StartBotConversation( string contents)
{
string directLineSecret = "secret";
string fromUser = "microserviceNotifyEndpoint";
Activity activity = new Activity
{
From = new ChannelAccount(fromUser),
Text = contents,
Type = ActivityTypes.Event
};
DirectLineClient client = new DirectLineClient(directLineSecret);
var conversation = await client.Conversations.StartConversationAsync();
await client.Conversations.PostActivityAsync(conversation.ConversationId, activity);
}
Basicly the execution get's stuck at var conversation = await client.Conversations.StartConversationAsync(); , it just waits forever.
I tried changing it to var conversation = await client.Conversations.StartConversationAsync().ConfigureAwait(continueOnCapturedContext: false);´the execution goes on but the activity doesn't seem to get posted.
I'm not sure why the call to .StartConversationAsync() would freeze in your case. Maybe you haven't enabled the Direct Line channel on dev.botframework.com/bots? Nonetheless, as pointed out by Sergey, the Direct Line is a Channel and not a means for communicating with your bot on other channels.
Check out the Connector Client: bot-builder-dotnet-connector
Here is a static example of using it to proactively send a message to a user from a bot: MicrosoftDX/botFramework-proactiveMessages - sample: ConversationStarter.cs
pertinent code from sample:
public static async Task Resume(string conversationId,string channelId)
{
var userAccount = new ChannelAccount(toId,toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync( botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, this is a notification";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
}
The serviceUrl, the channelId, conversationId, toId, fromId, etc are cached from previous communication by the user to the bot (these are statically stored in this example, so only work for one user). This example shows how it is possible to proactively send a message to a user from a bot. The Direct Line api is not required.
You don't need to use DirectLine, it is designed for creating alternative bot UIs.
To implementing what your want, you may try the following:
First, you need to store users addresses to whom you want to send the messages. It my be done by storing the ResumptionCookie of a user last message in your backend database.
var state = new ResumptionCookie(message).GZipSerialize();
When your PostConversationsEvents is called, you may resume the conversation at the latest point with each users.
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = resumptionCookie.GetMessage();
message.Text = content;
await Conversation.ResumeAsync(resumptionCookie, message);
It is not the only solution. As I said, in this case you just resumed the conversation with the user at the latest point. Another solution is to save the user address (user the same ResumptionCookie class) but start the conversation when you need to:
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = cookie.GetMessage();
ConnectorClient client = new ConnectorClient(new Uri(message.ServiceUrl));
var conversation = await
client.Conversations.CreateDirectConversationAsync(message.Recipient, message.From);
message.Conversation.Id = conversation.Id;
var newMessage = message.CreateReply();
newMessage.Text = content;
await client.Conversations.SendToConversationAsync(newMessage);
See more details on BotFramework documentation.

RabbitMQ not throwing error on invalid routing key

I have the following code in C#, which does not throw error if the routing key is invalid.
var connFactory = GetConnectionFactory();
using (var conn = connFactory.CreateConnection())
{
using (var channel = conn.CreateModel())
{
channel.TxSelect();
var publicationAddress = new PublicationAddress(ExchangeType.Direct, Settings.ServiceBusExchange, Settings.ServiceBusRoutingKey);
var headers = new Dictionary<String, Object>();
headers.Add("TransactionID", transactionID);
var basicProperties = new BasicProperties();
basicProperties.ContentEncoding = Encoding.UTF8.ToString();
basicProperties.ContentType = "text/xml";
basicProperties.Headers = headers;
basicProperties.DeliveryMode = 2;
var payLoad = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(publicationAddress, basicProperties, payLoad);
channel.TxCommit();
}
}
My question is, how can I make the code throw error if the routing key is invalid? Like when I Publish a message using RabbitMQ UI with invalid routing key, it gives a message "Message published, but not routed."
Thanks in advance.
it does not exist the concept of "invalid routing key", since you can bind dynamically queues to the exchanges.
Btw what you are looking for is "unroutable messages", you have to use the mandatory flag and implement the ReturnListener in the same channel, if a message does not reach any queue will be redirect to the handler.
In this in this way (the code is Java, but in c# is more or less the same):
boolean isMandatory = true; // if true the message will be handled by HandlingReturnListener
// if false the message will be dropped!
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(replyText + ":" + replyCode);
System.out.println("******** UnHandled Message ***************");
}
});
String myExchange = "myUnroutableExchange_";
channel.exchangeDeclare(myExchange, "topic", false, false, null);
channel.basicPublish(myExchange, "NO_KEY", isMandatory, null, "".getBytes());
For this there is something called PublisherAcknoledgement. This will basically gives an Ack to the publisher about the status of the message. You will be able to also differentiate between whether the message has reached till Exchange or it has reached till the consumer. You just have to handle each case properly.
This is a good way to know the status of the message being delivered. You might not know if its happening because of the wrong routing key but with doing various checks you might be able to narrow down to the result.

Set up a subscription for messages with no matching filter in Azure Service Bus (on-premise)

I'm using Azure Service Bus 1.1 (the on premise version)
I'm trying to set up a subscription that will receive messages that have not been filtered into any other existing subscription.
I have 3 console apps, one that creates topics and subscriptions, one that sends messages to the topic, and one that receives messages from a subscription.
I'm also using the Service Bus Explorer (V2.1) to see what is happening with my console apps.
I have tried setting up the topic as described on this page and this page which uses a MatchNoneFilterExpression but the example code does not compile(?) ie the FilterAction and FilterExpression properties are not in the RuleDescription class
RuleDescription matchNoneRule = new RuleDescription()
{
FilterAction = new SqlFilterAction("set defer = 'yes';"),
FilterExpression = new MatchNoneFilterExpression()
};
The RuleDescription class I'm using is in v2.1.0.0 of the Microsoft.ServiceBus.dll
It has the following properties available,
How do I send a message that matches no other filters to a particular subscription?
From this page which suggests setting the EnableFilteringMessagesBeforePublishing property on the topic.
It then suggests that on sending a message to this topic a message will trigger the NoMatchingSubscriptionException
I'm creating my topic with this code
var myTopic = new TopicDescription(topicName)
{
EnableFilteringMessagesBeforePublishing = true
};
namespaceManager.CreateTopic(myTopic);
I'm sending a message to the topic that doesn't match any filters and I can catch the exception and potentially resend the message with a property that does match a filter, e.g.:
try
{
topicClient.Send(message);
Console.WriteLine(string.Format("Message sent: Id = {0}, Body = {1}", message.MessageId, message.GetBody<string>()));
}
catch (NoMatchingSubscriptionException ex)
{
string messageBody = message.GetBody<string>();
BrokeredMessage msg = new BrokeredMessage(messageBody);
msg.Properties.Add("Filter", "NoMatch");
foreach (var prop in message.Properties)
{
msg.Properties.Add(prop.Key, prop.Value);
}
topicClient.Send(msg);
Console.WriteLine("\n NoMatchingSubscriptionException - message resent to NoMatchingSubscription");
Console.WriteLine(string.Format("Message sent: Id = {0}, Body = {1}", msg.MessageId, msg.GetBody<string>()));
}

Categories