MassTransit: Initialize consumer constructor with IRequestClient - c#

1) Hi. I'm learning MassTransit with RabbitMQ, but stuck with using Request/Respond. I read a lot of articles and try to write console app using MassTransit documentation. But still can't find any information about initializing consumer with IRequestClient interface. Here is My code:
static void Main(string[] args){
var serviceAddress = new Uri("loopback://localhost/notification.service");
var requestTimeout = TimeSpan.FromSeconds(120);
var bus = BusConfigurator.ConfigureBus((cfg, host) =>
{
cfg.ReceiveEndpoint(host, RabbitMqConstants.NotificationServiceQueue, e =>
{
e.Consumer(() => new OrderRegisteredConsumer(???));
});
});
IRequestClient<ISimpleRequest, ISimpleResponse> client = new MessageRequestClient<ISimpleRequest, ISimpleResponse>(bus, serviceAddress, requestTimeout);
bus.Start();
Console.WriteLine("Listening for Order registered events.. Press enter to exit");
Console.ReadLine();
bus.Stop();
}
And my consumer
public class OrderRegisteredConsumer: IConsumer<IOrderRegisteredEvent>
{
private static IBusControl _bus;
IRequestClient<ISimpleRequest, ISimpleResponse> _client;
public OrderRegisteredConsumer(IRequestClient<ISimpleRequest, ISimpleResponse> client)
{
_client = client;
}
public async Task Consume(ConsumeContext<IOrderRegisteredEvent> context)
{
await Console.Out.WriteLineAsync($"Customer notification sent: Order id {context.Message.OrderId}");
ISimpleResponse response = await _client.Request(new SimpleRequest(context.Message.OrderId.ToString()));
Console.WriteLine("Customer Name: {0}", response.CustomerName);
}
}
How can I put my client inside
e.Consumer(() => new OrderRegisteredConsumer(???));
2) I Also try to find some information about Request/Respond in sagas, but, unfortunately, all I find is https://github.com/MassTransit/MassTransit/issues/664
I will appreciate If someone have an example of using this in sagas, or if someone could provide some links, where I can read about this more.

You need the client variable to be available, but the client doesn't need to be ready at the moment you configure the endpoint. endpoint.Consumer does not instantiate the consumer straight away, it just needs a factory delegate, which will instantiate the consumer when a message comes for this consumer.
Since the delegate is a reference type, you can assign it later in your code.
So this would work:
IRequestClient<ISimpleRequest, ISimpleResponse> client;
var bus = BusConfigurator.ConfigureBus((cfg, host) =>
{
cfg.ReceiveEndpoint(host, RabbitMqConstants.NotificationServiceQueue, e =>
{
e.Consumer(() => new OrderRegisteredConsumer(client));
});
});
client = new MessageRequestClient<ISimpleRequest, ISimpleResponse>(
bus, serviceAddress, requestTimeout);

Related

Configure Deduplication on Azure Service Bus Queue with MassTransit

I have found the following question (How to configure the RequiresDuplicateDetection for AzureServiceBus topics) about how to set the RequiresDuplicationDetection property when configuring a publish topic from a producer application in MassTransit. However, I have not been able to find out how to do it for commands that are transmitted to a queue with Send rather than Publish.
Additionally, I have found that when configuring a consumer of one the queues in question I can set the property easily, as shown below. This however is not ideal for my use case, if possible I would much rather the producer set this property when it starts and creates the queue.
cfg.ReceiveEndpoint(queue, e =>{
e.RequiresDuplicateDetection = true;
e.ConfigureConsumer<JobEventConsumer>(registrationContext, consumerConfig =>{
consumerConfig.UseMessageRetry(r =>{
r.Interval(10, TimeSpan.FromMilliseconds(200));
r.Ignore<ValidationException>();
});
});
});
Update: After a bit more investigation I have also found that setting the property to true at the global config level doesn't seem to work either. Code shown below
class Program {
static async Task Main(string[] args) {
EndpointConvention.Map<ExtractionRequest>(new Uri("queue:test-queue"));
var busControl = Bus.Factory.CreateUsingAzureServiceBus(cfg =>{
cfg.Host("My connection string");
cfg.RequiresDuplicateDetection = true;
cfg.EnablePartitioning = true;
});
await busControl.StartAsync();
try {
do {
string value = await Task.Run(() =>{
Console.WriteLine("Enter message (or quit to exit)");
Console.Write("> ");
return Console.ReadLine();
});
if ("quit".Equals(value, StringComparison.OrdinalIgnoreCase)) break;
await busControl.Send<ExtractionRequest>(new {});
}
while (true);
}
finally {
await busControl.StopAsync();
}
}
}
public interface ExtractionRequest {}
Any advice on how to turn RequiresDuplicationDetection on for a queue from the producer is welcomed.
Thanks in advance, James.
You can't set queue properties from a message sender, it's the responsibility of the receive endpoint.
The receive endpoint is the responsible component because it's declaring the queue and related attributes.
The reason publish is different is because topics can be configured by the producer, since there may be multiple consumer subscriptions on a single topic.

mqttnet client not getting subscribed topics

I'm using mqtt.net (https://github.com/chkr1011/MQTTnet) and have written a small class to handle my mqtt client. The client connects to the broker and publishes it's messages successfully. Now I want the client to also react on some topics I subscribe the client to. But this does not seem to work. I do not get any breakpoint hit. This are the relevant parts of my code:
public async Task StartAsync(CancellationToken cancellationToken)
{
//Building the mqtt config
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.Server, MqttConfig.Port)
.WithClientId("HeaterService")
.WithCredentials(MqttConfig.User, MqttConfig.Password)
.WithTls(tlsParameters =>
{
tlsParameters.AllowUntrustedCertificates = true;
})
.WithCleanSession()
.Build();
//Getting an mqtt Instance
MqttClient = new MqttFactory().CreateMqttClient();
//Wiring up all the events...
MqttClient.UseApplicationMessageReceivedHandler( e => { HandleMessageReceived(e.ApplicationMessage); });
MqttClient.UseConnectedHandler(/*async*/ e =>
{
Console.WriteLine("### CONNECTED WITH BROKER ###");
});
await MqttClient.ConnectAsync(options);
}
The client connects successfully to the server and is possible to publish messages.
This is my messagehandler function:
private void HandleMessageReceived(MqttApplicationMessage applicationMessage)
{
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {applicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(applicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {applicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {applicationMessage.Retain}");
Console.WriteLine();
}
This is my subscribe code:
public async Task SubscribeTopic(string topic)
{
var subscribeResult = await MqttClient.SubscribeAsync(new TopicFilterBuilder()
.WithTopic(topic)
//.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
Console.WriteLine("### SUBSCRIBED ###");
Console.WriteLine("### Result: " + subscribeResult.Items.FirstOrDefault()?.ResultCode);
Console.WriteLine("### Result: " + subscribeResult.Items.FirstOrDefault()?.TopicFilter);
}
I call this function of my class with "Home/Heater/control/*";
When I use mqtt-explorer to send a test message to the topic "Home/Heater/control/test"
the functionhandler HandleMessageReceived is never hit.
What I am doing wrong?
It seems like you currently don't call your SubscribeTopic(string topic) method in your StartAsync(CancellationToken cancellationToken) method.
Besides that i personally also ran into a few problems when starting out with MQTTNet.
Like #Linuxx said pay attention to the order in wich you call subscribe and connect.
I also recommend adding a Disconnected_Handler to your client to make sure the connection doesn't terminate without your knowing.
It was important to put the subscribe after the connect for me, else I was having the same problem as you.
objClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(ReceivedMessage);
string[] strTopics = { "test/log", "test/log2" };
MqttClientSubscribeOptions objSubOptions = new MqttClientSubscribeOptions();
List<TopicFilter> objTopics = new List<TopicFilter>();
foreach(string strTopic in strTopics)
{
TopicFilter objAdd = new TopicFilter();
objAdd.Topic = strTopic;
objTopics.Add(objAdd);
}
objSubOptions.TopicFilters = objTopics;
objClient.ConnectAsync(objOptions, CancellationToken.None).Wait();
objClient.SubscribeAsync(objSubOptions); //!!!!subscribe goes here!!!!

Why a simple configuration in MassTransit creates 2 queues and 3 exchanges?

I created a MassTransit quickstart program to interact with my localhost RabbitMQ:
namespace ConsoleApp1
{
public static class Program
{
public class YourMessage
{
public string Text { get; set; }
}
public static async Task Main(params string[] args)
{
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
sbc.ReceiveEndpoint(host, "test_queue", ep =>
{
ep.Handler<YourMessage>(async context => await Console.Out.WriteLineAsync($"Received: {context.Message.Text}"));
});
});
await bus.StartAsync();
await bus.Publish(new YourMessage{Text = "Hi"});
Console.WriteLine("Press any key to exit");
Console.ReadKey();
await bus.StopAsync();
}
}
}
Everything looked fine untill I actually checked the underlying RabbitMQ management and found out that just for this very simple program, MassTransit created 3 exchanges and 2 queues.
Exchanges, all fanouts:
ConsoleApp1:Program-YourMessage: Durable
VP0003748_dotnet_bus_6n9oyyfzxhyx9ybobdmpj8qeyt: Auto-delete and Durable?
test_queue: Durable
Queues:
VP0003748_dotnet_bus_6n9oyyfzxhyx9ybobdmpj8qeyt: x-expire 60000
test_queue: Durable
I would like to know why all of that is necessary or is the default configuration? In particular, I am not really sure to get the point of creating so "many".
It is all described in the documentation.
ConsoleApp1:Program-YourMessage is the message contract exchange, here messages are being published.
test_queue is the endpoint exchange. It binds to the message exchange. This way, when you have multiple consumers for the same message type (pub-sub), they all get their copy of the message.
test_queue is the queue, which binds to the endpoint exchange. Publish-subscribe in RMQ requires exchanges and queues can find to exchanges, so messages get properly delivered.
Both non-durable queue and exchange with weird names are the endpoint temp queue and exchange, which are used for request-response.

.NET Client - Waiting for an MQTT response before proceeding to the next request

I have a MQTT calls inside a loop and in each iteration, it should return a response from the subscriber so that I could use the value being forwarded after I published. But the problem is I don't know how would I do it.
I hope you have an idea there or maybe if I'm just not implementing it right, may you guide me through this. Thanks.
Here's my code:
// MyClientMgr
class MyClientMgr{
public long CurrentOutput { get; set; }
public void GetCurrentOutput(MyObjectParameters parameters, MqttClient client)
{
MyMessageObject msg = new MyMessageObject
{
Action = MyEnum.GetOutput,
Data = JsonConvert.SerializeObject(parameters)
}
mq_GetCurrentOutput(msg, client);
}
private void mq_GetCurrentOutput(MyMessageObject msg, MqttClient client)
{
string msgStr = JsonConvert.SerializeObject(msg);
client.Publish("getOutput", Encoding.UTF8.GetBytes(msgStr),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
client.MqttMsgPublishReceived += (sender, e) =>{
MyObjectOutput output = JsonConvert.DeserializeObject<MyObjectOutput>(Encoding.UTF8.GetString(e.Message));
CurrentOutput = output;
};
}
}
// MyServerMgr
class MyServerMgr
{
public void InitSubscriptions()
{
mq_GetOutput();
}
private void mq_GetOutput()
{
MqttClient clientSubscribe = new MqttClient(host);
string clientId = Guid.NewGuid().ToString();
clientSubscribe.Connect(clientId);
clientSubscribe.Subscribe(new string[] { "getOutput" }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
MqttClient clientPublish = new MqttClient(host);
string clientIdPub = Guid.NewGuid().ToString();
clientPublish.Connect(clientIdPub);
clientSubscribe.MqttMsgPublishReceived += (sender, e) => {
MyMessageObj msg = JsonConvert.DeserializeObject<MyMessageObj>(Encoding.UTF8.GetString(e.Message));
var output = msg.Output;
clientPublish.Publish("getOutput", Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(output)), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
}
}
// MyCallerClass
class MyCallerClass
{
var host = "test.mqtt.org";
var myServer = new MyServerMgr(host);
var myClient = new MyClientMgr();
myServer.InitSubscriptions();
MqttClient client = new MqttClient(host);
for(int i = 0; i < 10; i++)
{
long output = 0;
MyObjectParameters parameters = {};
myClient.GetCurrentOutput(parameters, client) // here I call the method from my client manager
// to publish the getting of the output and assigned
// below for use, but the problem is the value doesn't
// being passed to the output variable because it is not
// yet returned by the server.
// Is there a way I could wait the process to
// get the response before assigning the output?
output = myClient.CurrentOutput; // output here will always be null
// because the response is not yet forwarded by the server
}
}
I have a loop in my caller class to call the mqtt publish for getting the output, but I have no idea how to get the output before it was assigned, I want to wait for the response first before going to the next.
I've already tried doing a while loop inside like this:
while(output == 0)
{
output = myClient.CurrentOutput;
}
Yes, I can get the output here, but it will slow down the process that much. And sometimes it will fail.
Please help me. Thanks.
It looks like you are trying to do synchronous communication over an asynchronous protocol (MQTT).
By this I mean you want to send a message and then wait for a response, this is not how MQTT works as there is no concept of a reply to a message at the protocol level.
I'm not that familiar with C# so I'll just give an abstract description of possible solution.
My suggestion would be to use a publishing thread, wait/pulse (Look at the Monitor class) to have this block after each publish and have the message handler call pulse when it has received the response.
If the response doesn't contain a wait to identify the original request you will also need a state machine variable to record which request is in progress.
You may want to look at putting a time out on the wait in case the other end does not respond for some reasons.
You can use AutoResetEvent class that has WaitOne() and Set() methods. Using WaitOne() after publish will wait until the message is published and using Set() under client_MqttMsgPublishReceived event will release the wait when the subscriber received the message he subscribed for.

Get queue name from inside QueueClient.OnMessage callback

QueueClient.OnMessage takes a callback, Action<BrokeredMessaged>, as an argument that will be executed by an internal message pump that's constantly polling a queue (or subscription) when a message is available.
I've been looking at the BrokeredMessage type in Reflector but can't find a way to get the queue name that the message came from the BrokeredMessage object (that last part is key). If this is possible, how can it be pulled out?
Finally figured out a solution using reflection:
public void OnMessageCallback(BrokeredMessage message) {
var context = message.GetType().GetProperty("ReceiveContext", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(message);
var receiver = (MessageReceiver)context.GetType().GetProperty("MessageReceiver", BindingFlags.Public | BindingFlags.Instance).GetValue(context);
var queueName = receiver.Path;
}
If you are using the QueueClient.OnMessage, you can do something like that:
var client = QueueClient.CreateFromConnectionString("MyConnectionString");
client.OnMessage(message =>
{
// You always have access to the queue path
var queueName = client.Path;
});
If you don't want to use anonymous function you can pass the queueName to the function that is going to process you message:
public void ProcessMessage(BrokeredMessage message, string queueName)
{
}
And call you function like that:
var client = QueueClient.CreateFromConnectionString("MyConnectionString");
client.OnMessage(message =>
{
ProcessMessage(message , client.Path);
});
EDIT : Using a MessageReceiver
Azure ServiceBus SDK provides an abstraction to receive messages from queues or subscriptions:
var messagingFactory = MessagingFactory.CreateFromConnectionString("MyConnectionString");
var messageReceiver = messagingFactory.CreateMessageReceiver("MyQueueName");
messageReceiver.OnMessage(message =>
{
// You always have access to the queue path
var queueName = messageReceiver.Path;
}, new OnMessageOptions());

Categories

Resources