This question is not about Scaleout with SignalR and Azure Service Bus. I want to build a Service Bus listener (e.g. OnMessage) into my SignalR web socket app that then distributes the message accordingly to connected users. Messages will be posted from various separately running services into the centralized Service Bus and the UI/browser connected to the web socket servers should receive these.
Option 1: I can add an async Task into a hub method to subscribe to a Service Bus and filter by the connected user. The problem with this is it uses an extra thread from the thread pool and will do this for every socket connection the user has started. Our app can easily start 5-10 or more sockets for every tab open.
Option 2: I can add a single task to the SignalR Startup.Configuration method that then receives all messages and distributes them to the proper connected users. The problem I've encountered here is that I don't have access to the Clients object used for sending to the browser.
I feel like SignalR and Service Bus are a good complement to each other to enable near real-time communications but I can find very little to implement a scenario like this. And I feel like this should be a common enough scenario. Perhaps I'm missing some obvious design patterns that would be a better solution.
I was able to figure this out. In the SignalR Startup.Configuration method I added a method to start the listener and in that method I called GlobalHost.ConnectionManager.GetHubContext. Currently this doesn't send to individual users but I'll add a connection manager of some sort to handle that.
public void startServiceBusListener()
{
// setup subcsription
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
if (!namespaceManager.SubscriptionExists("myTopic", Environment.MachineName))
namespaceManager.CreateSubscription("myTopic", Environment.MachineName);
SubscriptionClient busClient = SubscriptionClient.CreateFromConnectionString(connectionString, "myTopic", Environment.MachineName);
// Configure the callback options.
OnMessageOptions options = new OnMessageOptions();
options.AutoComplete = false;
options.AutoRenewTimeout = TimeSpan.FromMinutes(1);
receiveTask = Task.Run(() =>
{
// handle new messages
busClient.OnMessage((message) =>
{
try
{
Notification note = message.GetBody<Notification>();
string notification = JsonConvert.SerializeObject(note);
GlobalHost.ConnectionManager.GetHubContext<DispatchHub>().Clients.All.notify(notification);
// Remove message from subscription.
message.Complete();
}
catch (Exception)
{
// Indicates a problem, unlock message in subscription.
message.Abandon();
}
}, options);
}, cts.Token);
}
Related
I'm trying to implement "Notification System" from my Application Service.
I would like to alert a user using a toast via abp.notify.info() when the required processing (managed by Application Service) has been successfully completed.
Server side:
public async Task<bool> EsitoAllestimento(EsitoAllestimento data)
{
await _notiticationPublisher.PublishAsync(
"message"",
null,
null,
NotificationSeverity.Info
);
}
Client side:
abp.event.on('abp.notifications.received', function (userNotification) {
abp.notify.info('message', 'text');
});
UPDATE
At the moment, I use a different strategy based on the use of email messages from application layer.
In fact, EsitoAllestimento (WebApi method) is invoked by an external application to update some data in the web application (and informs the connected user of the processing result).
Now I'm planning to use SignalR communication, because email communication sometimes fails.
I have defined the hub class like ABP's SignalR Integration example and everything works fine, except communication from SERVER to CLIENT.
How can I use myChatHub at the Application layer or WebApi layer ?
From the documentation on Notification System:
There are two ways of sending notifications to users:
The user subscribes to a specific notification type. Then we publish a notification of this type which is delivered to all subscribed users. This is the pub/sub model.
We can directly send a notification to target user(s).
The Abp.AspNetCore.SignalR package implements IRealTimeNotifier to send real-time notifications to clients automatically, so you don't have to implement and call a custom Hub .
In your case, you should specify the target user.
The first argument is the notification name.
The second argument is where your "message" should go.
The userIds argument is where you should specify the target user.
For example, this shows how to send a message to the default tenant admin and the host admin:
var message = "message";
var defaultTenantAdmin = new UserIdentifier(1, 2);
var hostAdmin = new UserIdentifier(null, 1);
await _notificationPublisher.PublishAsync(
"App.SimpleMessage",
new MessageNotificationData(message),
severity: NotificationSeverity.Info,
userIds: new[] { defaultTenantAdmin, hostAdmin }
);
You don't have to implement the event listener on the client side as abp.notify.info is the default.
Full disclosure: I am new to the Service Fabric development. Here is my situation. We have Service Fabric Cluster. We deployed stateful service there. Service has designated Service Bus queue, which it listens to. So all service instances on all nodes in the cluster listening to the same Service Bus queue. Each service instance register OnMessage callback with the Service Bus queue to process message like this:
QueueClient Queue = QueueClient.CreateFromConnectionString(
GetServicebusConnectionString(),
ConfigData.SERVICE_QUEUE_NAME);
if (Queue != null)
{
var options = new OnMessageOptions();
options.AutoComplete = false;
Queue.OnMessage((receivedMessage) =>
ProcessMessage(receivedMessage), options);
}
Now, according to messages in the log it looks like all service instances pick up message, that has been placed in the queue simultaneously. Which does not constitute a good thing.
Question is:
Is it possible to use Service Bus queue in a way when each message from the queue would be picked up only by one service instance?
The default receive mode of queue client is PeekLock, and you set AutoComplete property to false, which will not automatically delete message after the client has received the message. After the lock expires, the message will become available again, and other service instances could receive and process it again.
Is it possible to use Service Bus queue in a way when each message from the queue would be picked up only by one service instance?
You could set AutoComplete property to true, or call Complete method after the client has received and processed the message.
Queue.OnMessage((receivedMessage) =>
{ ProcessMessage(receivedMessage); receivedMessage.Complete(); }, options);
I am using Azure ServiceBus to process items sent by multiple clients.
I am testing the part which receives these messages and have run 2 listeners side by side. However if I submit 2 items to the queue. Only 1 listener ever works and the second doesn't pick up the other item.
I have however tried running 2 listeners on different machines and they both process the 2 items pushed to the queue.
Is the issue with running multiple listeners on the same machine and if so, what am i doing wrong and how do i fix?
Thanks for your time.
Dan
Is the issue with running multiple listeners on the same machine
I create a console application to receive messages from Service Bus queue, and then I open and run program twice on my machine, both of queue clients/receivers could receive and process messages from same queue.
Call OnMessage method:
var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
var options = new OnMessageOptions();
options.AutoComplete = false;
client.OnMessage(mes =>
{
Console.WriteLine(mes.GetBody<string>());
}, options);
Output:
Call Receive method:
var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
BrokeredMessage mes = client.Receive();
Console.WriteLine(mes.GetBody<string>());
Output:
If possible, please share us your code, and then we will reproduce the issue based on your code.
I have a asynchronous method that is responsible for only connecting to the queue and reading from the queue. While trying to read I get an error from the WebSphere dll 'amqmdnet.dll' that says "Thread was being aborted". I get this error every time I try to modify the queue in any way, and only when I try to modify from an asynchronous method. I've also tried implementing the IBM.XMS.net dll as this was said to be used for asynchronous messaging, although I get the same error. Is it possible to read a queue from inside an async method? If so, is the implementation for reading/writing different for synchronous and asynchronous when it comes to modifying the queue itself? I can connect to the queue manager just fine, its modifying the queue that's giving me issues.
Main:
private async Task ReadAsync()
{
await MqMessanger.ConnectAsync(); // connects fine
await MqMessanger.StartReadingAsync(); // errors out
}
MqMessanger: (IBM.XMS.net dll)
private XMSFactoryFactory factoryFactory; //used for connection
private IConnectionFactory cf; //used for connection
private ISession sessionWMQ;
private IDestination destination;
private IMessageConsumer consumerAsync;
private MessageListener messageListener;
public IConnection QueueManager { get; set; }
//QueueManager has been connected prior to this
private void StartReadingAsync()
{
try
{
//Creates a session where an Ack is sent to the server to delete the message as soon the message is received.
sessionWMQ = QueueManager.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
destination = sessionWMQ.CreateQueue(queueName);
// Create consumer object to read messages from the queue
consumerAsync = sessionWMQ.CreateConsumer(destination);
// Create a message listener to fire when a message is put on the queue and assign it to consumer
messageListener = new MessageListener(OnMessageCallback);
consumerAsync.MessageListener = messageListener;
}
catch (Exception ex)
{
throw new Exception($"Error reading from '{destination.Name}'.", ex);
}
}
MqMessanger: (amqmdnet dll)
//QueueManager has been connected prior to this
private void StartReadingAsync()
{
try
{
queue = queueManager.AccessQueue(queueName, MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING);
queueMessage = new MQMessage();
queueMessage.Format = MQC.MQFMT_STRING;
queueGetMessageOptions = new MQGetMessageOptions();
queue.Get(queueMessage, queueGetMessageOptions);
string message = queueMessage.ReadString(queueMessage.MessageLength);
//Do something with this message
}
catch (Exception ex)
{
throw new Exception($"Error reading from '{destination.Name}'.", ex);
}
I think there is some confusion here between Asynchronous messaging pattern of IBM MQ and Asynchronous programming concept of .NET Framework.
IBM MQ enables application to application connectivity in an asynchronous way through queuing. In a typical client-server pattern, both client and server applications need to be up and running always for data exchange. This is synchronous pattern. In asynchronous pattern, client application and server application are not required to be running always. Client can send a message to MQ and go away. The message will be residing in a MQ queue. If the server application is running, that message will be delivered to server application. The server will process that message and put a reply message into MQ queue and go away. The client application can come back after some time and read the reply message. As you can see client and server applications are not simultaneously but still they are able to communicate in a asynchronous way via MQ.
I am not an expert in the newer .NET Framework concepts. The async programming concept in .NET Framework is for offloading short tasks that can be independently by the CPU without blocking a thread. For example when the main thread is busy displaying contents of a web page, the job of connecting to a database could be offloaded to a .NET task so that the user is able to interact with the web page.
IBM MQ .NET (amqmdnet and XMS) use multiple threads for communicating with a queue manager. I am unsure if 'async' programming technique is suitable when multiple threads are involved. This could be the reason for "Thread was being aborted" error.
Using the code as shown here.. I was able to create a web app that every 30 seconds sent data to client using System.Threading.Timer.
I was able to add some code which received data from a service bus queue using Messaging factory and Messaging receiver and based on that sent data to signalR client instead of hard-coding as in the mentioned example.
Now my real application gets data from 50 such queue..
Theoretically, I could create 50 timer objects which would call 50 different methods which in turn would call service bus queue.
I would sincerely appreciate if someone could suggest the right way to achieve my goal..
Thanks
The message pump pattern seems like it would be a good fit for this application. You create a separate client for each queue and configure each one to automatically listen for messages in its queue and process them as they come in.
foreach (var queueName in queueNames){
var queueClient = QueueClient.CreateFromConnectionString(connectionString, queueName);
queueClient.OnMessage(message =>
{
// Do work here
Console.Out.WriteLine(string.Format("Recieved message {0} on queue {1}", message.MessageId, queueName));
});
}