I am trying to connect from my C# code to IBM MQ.
Callback works for one listener (listening to a queue). i.e. when a message comes to the queue, callback method is correctly invoked.
I created another queue#2 on the same QueueManager / Channel. Registered a new listener on this queue with a different callback method. No errors are observed during server startup.
When a message arrives at this queue#2, callback associated with the first queue is invoked and not the one that I have registered for this queue. I tried by creating a different session, and also a different connection, but still the behaviour is the same.
Does anybody have any ideas?
class Program
{
static void Main(string[] args)
{
string env = "DEV"
string queueName1= "Queue1"
string queueName2 = "Queue2"
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
}
public class MyListener
{
public delegate void Handler (IMessage msg)
public static Handler _handler
private string env = "";
private string queue = "";
public MyListner(Handler _handler, string environment, string queueName)
{
_handler = _handler;
this.env = environment;
this.queue = queueName
}
public void RegisterListener()
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "10.87.188.156(7111)");
cf.SetIntProperty(XMSC.WMQ_PORT, 7111);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "QMEIGS1.CRM.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT_UNMANAGED);
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QMEIGS1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(OnMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
private void onMessage(IMessage m)
{
try {
_handler(m)
}
catch (Exception e )
{
}
}
}
//callback 1
public class CallbackHandler1
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage1
}
//callback 2
public class CallbackHandler2
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage2
}
I am not a hardcore c# programmer but I think the problem is because of the static keyword in public static Handler _handler;. The keyword static makes only one instance of _handler available across all instances of a MyListener class. Although you are creating two consumers and assigning two listeners, the second call to assign the message listener is overwriting the first listener. Hence you are receiving messages from only one queue.
I modified your code little bit as it was not compiling and got it working after removing the static keyword. I used MQ v8 for compiling the program. But the version should not be a problem for you.
namespace multilistener
{
class Program
{
static void Main(string[] args)
{
string env = "DEV";
string queueName1= "SUB.Q";
string queueName2 = "SUB.Q1";
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
System.Threading.Thread.Sleep(30000);
Console.WriteLine("Program ends");
}
}
public class MyListener
{
public delegate void Handler (IMessage msg);
public Handler _handler; // Remove 'static' keyword
private string env = "";
private string queue = "";
public MyListener(Handler _Inhandler, string environment, string queueName)
{
_handler = _Inhandler;
this.env = environment;
this.queue = queueName;
}
public void RegisterListener()
{
try
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "localhost");
cf.SetIntProperty(XMSC.WMQ_PORT, 1414);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "MY.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT);
cf.SetStringProperty(XMSC.USERID, "userid");
cf.SetStringProperty(XMSC.PASSWORD, "password");
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QM1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(onMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private void onMessage(IMessage m)
{
try {
_handler(m);
}
catch (Exception e )
{
Console.Write(e);
}
}
}
//callback 1
public class CallbackHandler1
{
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage1
Console.WriteLine("First consumer");
}
}
//callback 2
public class CallbackHandler2
{
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage2
Console.WriteLine("Second consumer");
}
}
}
Related
I have a receiver method in an ASP.NET Core web app:
public void ReceiveMessage()
{
using (var connection = CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "QueueName",durable: false,exclusive: false,autoDelete: false,arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
//Do something
channel.BasicAck(deliveryTag: ea.DeliveryTag,multiple: false);
};
channel.BasicConsume(queue: "QueueName",autoAck: false,consumer: consumer);
//BAD!!!
while (true)
{
}
}
}
You'll notice I am using while(true) which smells bad. I basically need to keep this method alive and wonder how have other people done it?
This method should stay alive all the time and automatically process messages 1 by 1
You can create a Hosted service and consume your message inside it. This Hosted service is always alive and can receive all the messages.
public class ProductTopicConsumerService : ConsumerBase, IHostedService
{
public ProductTopicConsumerService(ConnectionFactory connectionFactory)
: base(connectionFactory, ExchangeTypes.topic)
{
try
{
Consume<ProductCreatedIntegrationEvent>();
}
catch (Exception ex)
{
Console.WriteLine($"Error => {ex?.Message}");
}
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "Product.Updated5";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
public virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public virtual Task StopAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.CompletedTask;
}
}
and ConsumerBase could be like this :
public abstract class ConsumerBase : RabbitMqClientBase
{
public ConsumerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType,false)
{
}
protected void Consume<TMessage>()
{
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += OnEventReceived<TMessage>;
Channel.BasicConsume(queue: RefinedQueueName, autoAck: false, consumer: consumer);
}
private Task OnEventReceived<TMessage>(object sender, BasicDeliverEventArgs #event)
{
try
{
var body = Encoding.UTF8.GetString(#event.Body.ToArray());
var message = JsonConvert.DeserializeObject<TMessage>(body);
ReceiveAction(message);
Channel.BasicAck(#event.DeliveryTag, false);
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred in OnEventReceived. Message: {ex?.Message}");
Channel.BasicNack(#event.DeliveryTag, false, true);
throw;
}
finally
{
}
}
protected virtual void ReceiveAction<TMessage>(TMessage message)
{
}
}
and finally, rabbitMQBase could be something like this :
public abstract class RabbitMqClientBase : IDisposable
{
protected const string VirtualHost = "MQ";
protected abstract string Exchange { get; }
protected abstract string Queue { get; }
protected abstract string AppId { get; }
protected abstract string QueueAndExchangeRoutingKey { get; }
protected IModel Channel { get; private set; }
private IConnection _connection;
private readonly ConnectionFactory _connectionFactory;
private readonly ExchangeTypes _exchangeType;
private readonly bool _isPublisher;
protected RabbitMqClientBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType, bool isPublisher)
{
_connectionFactory = connectionFactory;
this._exchangeType = exchangeType;
this._isPublisher = isPublisher;
ConnectToRabbitMq();
}
protected internal string RefinedExchangeName => $"{VirtualHost}.{Exchange}";
protected internal string RefinedQueueName => $"{VirtualHost}.{Queue}";
protected internal string RefinedRoutingKey => $"{VirtualHost}.{QueueAndExchangeRoutingKey}";
private void ConnectToRabbitMq()
{
if (_connection == null || _connection.IsOpen == false)
{
_connection = _connectionFactory.CreateConnection();
}
if (Channel == null || Channel.IsOpen == false)
{
Channel = _connection.CreateModel();
Channel.ExchangeDeclare(exchange: RefinedExchangeName, type: _exchangeType.ToString(), durable: true, autoDelete: false);
if (!_isPublisher)
{
Channel.QueueDeclare(queue: RefinedQueueName, durable: true, exclusive: false, autoDelete: false);
Channel.QueueBind(queue: RefinedQueueName, exchange: RefinedExchangeName, routingKey: RefinedRoutingKey);
}
}
}
public void Dispose()
{
Channel?.Close();
Channel?.Dispose();
Channel = null;
_connection?.Close();
_connection?.Dispose();
_connection = null;
}
}
and this is Publisher|Prodcuer class for RabbitMQ :
public interface IRabbitMqProducer<in T>
{
PublishResult Publish(T #event);
}
public abstract class ProducerBase<T> : RabbitMqClientBase, IRabbitMqProducer<T>
{
protected ProducerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType, true) { }
public PublishResult Publish(T #event)
{
var result = new PublishResult();
try
{
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(#event));
var properties = Channel.CreateBasicProperties();
properties.AppId = AppId;
properties.ContentType = "application/json";
properties.DeliveryMode = 2; //persist mode
properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
Channel.BasicPublish(exchange: RefinedExchangeName, routingKey: RefinedRoutingKey, basicProperties: properties, body: body);
result.SetSuccess();
}
catch (Exception ex)
{
result.SetError(ex);
}
return result;
}
}
and for publishing your publisher should be like this:
public class ProductTopicProducerService : ProducerBase<ProductCreatedIntegrationEvent>
{
public ProductTopicProducerService(ConnectionFactory connectionFactory)
: base(connectionFactory,ExchangeTypes.topic)
{
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
}
and for publish a new message follow the below code:
var result = _producer.Publish(#event);
if (!result.IsSuccess)
{
Console.WriteLine($"Error => {result.Description}");
}
Offsource you can simplify your consumer. but this is my experience to create a base class for rabbitMQBase and now both Publisher and consumer could use it.
Take a look at the OWIN package - available in NuGet: Microsoft.Owin.Hosting
When you use it you are self-hosting the web service and your app would start with a call to:
WebApp.Start(queryServiceUrl, Startup);
Where "Startup" is a method to do all your initialization. Your app/service stays running and will accept queries on the specified URL.
Without a loop, the instant after you call channel.BasicConsume, the whole thing (connection/channel) will go out of scope and be immediately disposed/destroyed via the using statement. So, without the loop, your consumer doesn't actually consume anything.
To ensure the consumer operates, you need to have an infinite loop, with appropriate logic to exit when you shut down the program. This is an unfortunate design of the .NET library for RabbitMQ, but it is what it is.
while (_isRunning & channel.IsOpen) {
Thread.Sleep(1);
// Other application logic here; e.g. periodically break out of the
// loop to prevent unacknowledged messages from accumulating in the system
// (if you don't, random effects will guarantee that they eventually build up)
}
I am following this article (http://www.jarloo.com/listening-to-rabbitmq-events/) for consuming the messages on a console application, currently i am concerned about what happens when a user pressing CTRL + C and quits the application.
As a minimum i would prefer it complete processing the current message and ack before exiting the program. I am confused how can i implement this code as i am new to RabbitMQ.
I understand that channel.BasicConsume(queueName, true, consumer); is blocking thread.
Any help will be appreciated.
Here is what I managed to achieve but not sure if it is best way around or there is an improvement. I did not use the cancellationToken as yet not sure if we can use it.
On my console i get the expected result (see screenshot)
public abstract class QueueConnection : IDisposable
{
internal IConnection _connection;
internal IModel _model;
internal IBasicProperties _properties;
internal RabbitMQSettings _settings;
internal protected object _modelLock = new Object();
public QueueConnection(IOptions<RabbitMQSettings> queueConfig)
{
_settings = queueConfig.Value;
}
internal bool CreateModel(string queueName)
{
if (string.IsNullOrEmpty(queueName))
{
throw new ArgumentException("The queue name has to be specified before.");
}
lock (_modelLock)
{
if (!IsConnected) Connect();
if (_model == null || _model.IsClosed)
{
_model = _connection.CreateModel();
// When AutoClose is true, the last channel to close will also cause the connection to close.
// If it is set to true before any channel is created, the connection will close then and there.
_connection.AutoClose = true;
// Configure the Quality of service for the model. Below is how what each setting means.
// BasicQos(0="Dont send me a new message untill I’ve finshed", 1= "Send me one message at a time", false ="Apply to this Model only")
_model.BasicQos(0, 50, false);
const bool durable = true, queueAutoDelete = false, exclusive = false;
_model.QueueDeclare(queueName, durable, exclusive, queueAutoDelete, null);
_properties = RabbitMQProperties.CreateDefaultProperties(_model);
}
}
return true;
}
public void Connect()
{
var connectionFactory = new ConnectionFactory
{
HostName = _settings.HostName,
UserName = _settings.UserName,
Password = _settings.Password,
};
if (_settings.Port.HasValue) connectionFactory.Port = _settings.Port.Value;
if (_settings.Heartbeat.HasValue) connectionFactory.RequestedHeartbeat = _settings.Heartbeat.Value;
if (!string.IsNullOrEmpty(_settings.VirtualHost)) connectionFactory.VirtualHost = _settings.VirtualHost;
_connection = connectionFactory.CreateConnection();
}
public bool IsConnected
{
get { return _connection != null && _connection.IsOpen; }
}
public object GetConnection()
{
return _connection;
}
public void Disconnect()
{
if (_connection != null) _connection.Dispose();
}
void IDisposable.Dispose()
{
Disconnect();
}
}
QueueConsumer Class
public class QueueConsumer : QueueConnection, IQueueConsumer
{
private EventingBasicConsumer consumer;
public QueueConsumer(IOptions<RabbitMQSettings> queueConfig)
:base(queueConfig) {}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError)
{
ReadFromQueue(onDequeue, onError, _settings.QueueName);
}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError, string queueName)
{
CreateModel(queueName);
consumer = new EventingBasicConsumer(_model);
// Receive the messages
consumer.Received += (o, e) =>
{
try
{
var queueMessage = Encoding.UTF8.GetString(e.Body);
onDequeue.Invoke(queueMessage, e.DeliveryTag);
}
catch (Exception ex)
{
onError.Invoke(ex, e.DeliveryTag);
}
};
// if the consumer shutdown reconnects to rabbitmq and begin reading from the queue again.
consumer.Shutdown += (o, e) =>
{
CreateModel(queueName);
ReadFromQueue(onDequeue, onError, queueName);
};
_model.BasicConsume(queueName, false, consumer);
}
public void AcknowledgeMessage(ulong deliveryTag)
{
if (!IsConnected) Connect();
CreateModel(_settings.QueueName);
_model.BasicAck(deliveryTag, false);
}
public void StopListening()
{
_model.BasicCancel(consumer.ConsumerTag);
}
}
Main Class
static ManualResetEvent _quitEvent = new ManualResetEvent(false);
public static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role started");
var listener = serviceProvider.GetService<IMessageProcessor>();
Console.CancelKeyPress += (sender, eArgs) =>
{
listener.OnStop();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role finished");
_quitEvent.Set();
eArgs.Cancel = true;
};
_quitEvent.WaitOne();
}
private static IConfigurationRoot GetConfiguration()
{
// Build appsetting.json configuration
var environment = Environment.GetEnvironmentVariable("Environment");
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.AddEnvironmentVariables().Build();
}
private static void ConfigureServices(IServiceCollection services)
{
IConfigurationRoot configuration = GetConfiguration();
services.AddSingleton<IConfigurationRoot>(configuration);
// Support typed options
services.AddOptions();
services.Configure<RabbitMQSettings>(configuration.GetSection("RabbitMQConfig"));
services.AddSingleton<IQueueConsumer, QueueConsumer>();
services.AddScoped<IMessageProcessor, MessageProcessor>();
}
}
For the issue you are trying to get around only thing I can think of is treat this as a transaction.. Only when you get the message, process it completely and send the Ack back consider the transaction as completed.
If you process the message first and some one terminates the application before the Ack it will get queued again and when we restart the application it will get processed again.
Against that if you Ack first and then try to process the message and some one terminates the application you will lose the message.
So looks like considering the whole process as transaction will make it work or the other option is to take care of same message getting processed again.
I have an async service
The service contract defined like:
[ServiceBehavior(InstanceContextMode = InstanceContext.PerCall]
Myservice
My client is defined like:
MyServiceClient task= null;
InstanceContext instanceContext = new InstanceContext(this);
task = new MyServiceClient(instanceContext);
task.MyMethod();
And the client class implements the call back methods (finish, progress etc...).
It's works fine, but if I call to the method, and she start running on the server and I shut down the server,I can't know the status of my call, and the client still think that the methods still running.
So, how can I check if this call is still running?
Thanks for helpers :)
Edit:
CallBack Interface:
public interface IServiceCallback
{
[OperationContract(IsOneWay=true)]
void NotifyFinished();
[OperationContract(IsOneWay=true)]
void NotifyProgress(int x);
[OperationContract(IsOneWay=true)]
void NotifyFailed(Exception exception);
}
Service Interface:
[ServiceContract(CallbackContract = typeof (IServiceCallback)]
public interface IAsyncService
{
[OperationContract(IsOneWay=true)]
void AsyncRunning();
}
Service Class:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class AsyncService : IAsyncService
{
private IServiceCallback ServiceCallback {get; set;}
public void AsyncRunningProxy ()
{
for(int x=0; x<100 ; x++)
{
AsyncService.NotifyProgress(x);
}
}
private void EndMethod(IAsyncResult res)
{
AsyncResult result = (AsyncResult)res;
try
{
((dynamic)result.AsyncDelegate).EndInvoke(res);
AsyncService.NotifyFinished();
}
catch (Exception e)
{
AsyncService.NotifyFailed(e);
}
}
public void AsyncRunning ()
{
ServiceCallback = OperationContext.Current.GetCallBackChannel<IServiceCallback>();
Action action = AsyncRunningProxy;
action.BeginInvoke(EndMethod, null);
}
}
Client Class:
public class ServiceRunner : IServiceCallback
{
private ManualResetEvent reset {get; set;}
public ServiceRunner()
{
reset = new ManualResetEvent(false);
}
public void Run()
{
AsyncServiceClient client = null;
InstanceContext instanceContext = new InstanceContext(this);
client = new AsyncServiceClient(instanceContext);
client.AsyncRunning();
reset.WaitOne();
}
public void NotifyProgress(int x)
{
Console.WriteLine(x);
}
public void NotifyFinished()
{
}
public void NotifyFailed(Exception e)
{
Console.WriteLine(e.Message);
reset.Set();
}
}
Edit: new client Class:
Client Class:
public class ServiceRunner : IServiceCallback
{
private ManualResetEvent reset { get; set; }
private string IsRunning { get; set; }
public ServiceRunner()
{
reset = new ManualResetEvent(false);
IsRunning = true;
}
public void Run()
{
AsyncServiceClient client = null;
InstanceContext instanceContext = new InstanceContext(this);
client = new AsyncServiceClient(instanceContext);
client.AsyncRunning();
new Thread(()=>
{
while(IsRunning)
{
try
{
client.IsAlive();
Thrad.Sleep(60 * 1000);
}
catch (Exception e) // The server is not responding.
{
NotifyFailed(e);
return;
}
}
}).Start();
reset.WaitOne();
}
public void NotifyProgress(int x)
{
Console.WriteLine(x);
}
public void NotifyFinished()
{
IsRunning = false;
reset.Set();
}
public void NotifyFailed(Exception e)
{
IsRunning = false;
Console.WriteLine(e.Message);
reset.Set();
}
}
In order to have more control of your clients request to the service, you should be able to use the inbuilt Task and Async support to monitor and if necessary handle connection delays.
The support for generating Task-based operations on the client side will be available to users who rely on proxies generated by our client generation tools (svcutil.exe or Add Service Reference), as well as to users who prefer to directly use ChannelFactory
The following code provides a rough example:
Task<string> task = new MyServiceClient().MyMethod();
if (task == await Task.WhenAny(task, Task.Delay(1000)))
{
Console.WriteLine(await task);
}
else
{
// handle delay …
}
Refer to the following MSDN blog entry for more information:
http://blogs.msdn.com/b/endpoint/archive/2010/11/13/simplified-asynchronous-programming-model-in-wcf-with-async-await.aspx
Regards,
As #adkSerenity mention you may implement timeout logic, but I guess your question not about that.
The callback method will be(and should be) called in case of exception for example connection lose or internal connection time out.
private static void CallbackSample(IAsyncResult asynchronousResult)
{
try
{
// State of request is asynchronous.
RequestState myRequestState=(RequestState) asynchronousResult.AsyncState;
HttpWebRequest myHttpWebRequest2=myRequestState.request;
myRequestState.response = (HttpWebResponse);
//next line may throw exception
myHttpWebRequest2.EndGetResponse(asynchronousResult);
}
catch(WebException e)
{
}
}
So async communication looks like fire and forget. Your callback method will be called when you get the result(exception too), but if you decide not handle it(custom time out logic) you must "foret" about callback processing. No way to check is alive(except of course custom api).
I wet through this except that I added it to a windows service like this
public partial class TriggerHostProcesses : ServiceBase
{
private const string MESSAGE_QUEUE = #".\Private$\Sample Queue";
private MessageQueue _queue;
public TriggerHostProcesses()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
SendMessage("Hope This Works");
}
protected override void OnStop()
{
}
internal void start()
{
OnStart(null);
}
private void SendMessage(string message)
{
_queue = new MessageQueue(MESSAGE_QUEUE);
Message msg = new Message();
msg.Body = message;
msg.Label = "Testing " + DateTime.Now.ToString();
_queue.Send(msg,new MessageQueueTransaction());
}
}
and to get the message
partial class HostListener : ServiceBase
{
private const string MESSAGE_QUEUE = #".\Private$\Sample Queue";
private MessageQueue _queue;
public HostListener()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
var myTransaction = new MessageQueueTransaction();
var queue = new MessageQueue(MESSAGE_QUEUE);
var message = queue.Receive(new TimeSpan(0, 0, 20),myTransaction);
message.Formatter = new XmlMessageFormatter(
new String[] { "System.String,mscorlib" });
Console.WriteLine(message.Body.ToString());
}
catch(Exception ex)
{
Console.WriteLine("No Message");
}
}
protected override void OnStop()
{
// TODO: Add code here to perform any tear-down necessary to stop your service.
}
internal void start()
{
OnStart(null);
}
}
in my main I added this
var ServiceToRun1 = new TriggerHostProcesses();
var ServiceToRun2 = new HostListener();
if (Environment.UserInteractive)
{
// This used to run the service as a console (development phase only)
ServiceToRun1.start();
ServiceToRun2.start();
Console.WriteLine("Press Enter to terminate ...");
Console.ReadLine();
ServiceToRun1.Stop();
ServiceToRun2.Stop();
}
else
{
ServiceBase.Run(ServiceToRun1);
}
I get the exception Timeout for the requested operation has expired.
Can someone please check if they can see what the problem is?
I don't believe you are using transactions correctly. For example, when sending a message you use:
_queue.Send(msg,new MessageQueueTransaction());
However, this does not begin or commit a transaction. Looking in MSDN the example uses the following code (edited by me):
var myTransaction = new MessageQueueTransaction();
myTransaction.Begin();
myQueue.Send("hello world", myTransaction);
myTransaction.Commit();
I don't believe your message is getting sent, and so your Receive times out.
Similarly your receive logic doesn't seem to correctly use transactions:
myTransaction.Begin();
var myMessage = myQueue.Receive(myTransaction);
var body myOrder = (string)myMessage.Body;
myTransaction.Commit();
You should Rollback in the event of an exception processing your messages so they can be placed back on the queue.
Here is my final product. I'm using this in a windows service. 20 s at a time to see if I have a message then do my processes.
public class MSMQueue:IQueue
{
public MSMQueue(string queueName)
{
Message_Queue = queueName;
}
public string Message_Queue { get; private set; }
public string Pop()
{
MessageQueue queue = new MessageQueue(Message_Queue);
if (queue.Transactional)
return popTransactionalQueue(queue, new TimeSpan(0, 0, 1));
else
return popNormalQueue(queue, new TimeSpan(0, 0, 1));
}
public string Pop(TimeSpan timeSpan)
{
MessageQueue myQueue = new MessageQueue(Message_Queue);
if (myQueue.Transactional)
return popTransactionalQueue(myQueue, timeSpan);
else
return popNormalQueue(myQueue, timeSpan);
}
public void Add(string message)
{
// Connect to a queue on the local computer.
MessageQueue myQueue = new MessageQueue(Message_Queue);
// Send a message to the queue.
if (myQueue.Transactional)
{
var myTransaction = new MessageQueueTransaction();
myTransaction.Begin();
myQueue.Send(message, myTransaction);
myTransaction.Commit();
}
else
myQueue.Send(message);
}
#region private methods
private string popNormalQueue(MessageQueue queue, TimeSpan timeOut)
{
var message = queue.Receive(timeOut);
message.Formatter = new XmlMessageFormatter(
new String[] { "System.String,mscorlib" });
return message.Body.ToString();
}
private string popTransactionalQueue(MessageQueue queue, TimeSpan timeOut)
{
// Set the formatter.
queue.Formatter = new XmlMessageFormatter(new Type[]
{typeof(String)});
// Create a transaction.
MessageQueueTransaction myTransaction = new
MessageQueueTransaction();
String message=string.Empty;
try
{
myTransaction.Begin();
Message myMessage = queue.Receive(timeOut, myTransaction);
message = (String)myMessage.Body;
myTransaction.Commit();
}
catch (MessageQueueException e)
{
myTransaction.Abort();
throw e;
}
return message;
}
#endregion
}
I would like to implement a pub/sub application with .NET clients, so I'm testing SignalR by means of this minimal code.
This is the server:
namespace Test.SignalRComm.SimpleServer
{
using System.Threading.Tasks;
using log4net;
using SignalR;
using SignalR.Hosting.Self;
using SignalR.Hubs;
using SignalR.Infrastructure;
class Program
{
private static SignalRServer signalRServer = null;
static void Main(string[] args)
{
signalRServer = new SignalRServer();
signalRServer.Start();
System.Console.WriteLine("Press Enter to close...");
System.Console.ReadLine();
signalRServer.Stop();
}
}
public class SignalRServer
{
private string serverUrl = null;
public Server signalRServer = null;
public SignalRServer()
{
serverUrl = #"http://localhost:5001/";
signalRServer = new SignalR.Hosting.Self.Server(serverUrl);
signalRServer.EnableHubs();
}
public void Start()
{
signalRServer.Start();
}
public void Stop()
{
IConnectionManager connManager = signalRServer.DependencyResolver.Resolve<IConnectionManager>();
dynamic clients = connManager.GetClients<SignalRTestHub>();
clients.AddMessage("Test");
signalRServer.Stop();
}
}
public class SignalRTestHub : Hub, IDisconnect
{
private static readonly ILog logger = LogManager.GetLogger(typeof(SignalRTestHub));
public void Register(string token)
{
AddToGroup(token).ContinueWith(task =>
{
if (task.IsFaulted)
logger.Error(task.Exception.GetBaseException());
else
{
string message = string.Format("Client {0} registered with token <{1}>", Context.ConnectionId, token);
logger.Info(message);
}
});
}
public void Unregister(string token)
{
RemoveFromGroup(token).ContinueWith(task =>
{
if (task.IsFaulted)
logger.Error(task.Exception.GetBaseException());
else
logger.InfoFormat("Client {0} unregistered from token <{1}>", Context.ConnectionId, token);
});
}
public Task Disconnect()
{
string message = string.Format("Client {0} disconnected", Context.ConnectionId);
logger.Info(message);
return null;
}
}
}
and this is the client:
namespace Test.SignalRComm.SimpleClient
{
using System.Threading.Tasks;
using log4net;
using SignalR.Client.Hubs;
class Program
{
private static readonly ILog logger = LogManager.GetLogger(typeof(Program));
static void Main(string[] args)
{
SignalRClient client = new SignalRClient("http://localhost:5001/");
client.Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
logger.Error("Failed to start!", task.Exception.GetBaseException());
}
else
{
logger.InfoFormat("Success! Connected with client connection id {0}", client.ConnectionId);
// Do more stuff here
client.Invoke("Register", "Test").ContinueWith(tsk =>
{
if (tsk.IsFaulted)
logger.Error("Failed to start!", tsk.Exception.GetBaseException());
else
logger.Info("Success! Registered!");
});
}
});
System.Console.WriteLine("Press Enter to close...");
System.Console.ReadLine();
client.Invoke("Unregister", "Test").ContinueWith(tsk =>
{
if (tsk.IsFaulted)
logger.Error("Failed to stop!", tsk.Exception.GetBaseException());
else
logger.InfoFormat("Success! Unregistered!");
});
client.Stop();
}
}
public class SignalRClient : HubConnection
{
private static readonly ILog logger = LogManager.GetLogger(typeof(SignalRClient));
IHubProxy hub = null;
public SignalRClient(string url)
: base(url)
{
hub = CreateProxy("Test.SignalRComm.SimpleServer.SignalRTestHub");
}
public Task Invoke(string methodName, params object[] args)
{
return hub.Invoke(methodName, args);
}
public void AddMessage(string data)
{
logger.InfoFormat("Received {0}!", data);
}
}
}
While invoking hub methods from client (Register and Unregister) works fine, I'm not able to call client AddMessage method from hub.
Furthermore the Disconnect method of the hub is never called when a client is closed.
What I'm doing wrong? I'm not able to find any working example.
Edit
Subscribing to hubs events on the client like this:
hub.On<string>("Notify", message => Console.Writeline("Server sent message {0}", message);
and triggering the event on the hub like this:
Clients.Notify("Something to notify");
makes the notifications from server to clients working.
I'm still unable to detect a client disconnection. I implemented the IDisconnect interface on the hub, but when a client connection stops, the Disconnect method on the hub isn't triggered.
Thanks for your help.
Take a look at how to use the .NET client here:
https://gist.github.com/1324342
And API docs here:
https://github.com/SignalR/SignalR/wiki
TL;DR you need to subscribe to specific methods, deriving from the hubConnection doesn't make any magic happen.