Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client


In this article, I will showcase how to connect, produce, and consume messages to a RabbitMQ instance running as a docker container through the C# .NET client.
As a prerequisite, I assume you are familiar with C#, Docker, and RabbitMQ.
This article covers pulling a RabbitMQ image from dockerhub and then containerizing it; creating a dummy .NET Core Web API which produces messages into the RabbitMQ queue; and developing a dummy consumer console application that listens to the queue to consume messages.
The idea here is not to dwell too much into the intricacies and configurations of the RabbitMQ jargon, but to illustrate the way to connect to an instance running in a container through a dummy producer and consumer. 
So let's get started!

RabbitMQ Docker Image and Container Setup

As a first step, we should have the Docker Desktop downloaded and installed in our system.
After that, we would be running the following command to pull the desired RabbitMQ image from the respective DockerHub page. 
  1. docker pull rabbitmq:3-management   
This will pull the RabbitMQ image from the dockerhub with a tag of "3-management", which includes the management plug-in with this. Once the image is downloaded successfully, we can run that and put it into a container with the below command:
  1. docker run -d --hostname my-rabbit --name my-rabbit -p 15672:15672 -p  5672:5672 rabbitmq:3-management  
Let's now understand segment-by-segment what this script does...
This script creates a container with the hostname as "my-rabbit" and performs port mapping. This port mapping is a very important step because the underlying support of ports for the AMQP client to connect to RabbitMQ instance needs to be compatible with our desired port in the host.
Here, the TCP container port of 5672 and 15672 are mapped to the same ports in the host. As per the RabbitMQ documentation "5672, 5671: used by AMQP 0-9-1 and 1.0 clients without and with TLS". Our RabbitMQ instances should expose the supported AMQP port and our Producers and Consumers should be connected to that port for successful communication.
After successful execution of the above docker command, on "docker ps", we should be able to see the following container running:
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client
Next, let's set up our dummy Producer!

The Producer

Here, I have created an ASP.NET Core WebAPI project to simulate a producer. I have added a custom controller named: ConfigurationController which would expose a POST API for producing messages. First, let's see how we can connect to the running RabbitMQ container.
We would be using the NuGet package of the .NET RabbitMQ client: RabbitMQ.Client (at the time of writing this article, I had installed version 6.0.0).

Connecting to the Container

There are two ways we could initialize the ConnectionFactory to instantiate connection with the running RabbitMQ container.
Step 1
Using the host and port information along with credentials. For simplicity, I have not changed the default credential - which is set to 'guest' for both username and password. 
  1. var factory = new ConnectionFactory()  
  2.             {  
  3.                 HostName = "localhost",  
  4.                 UserName = ConnectionFactory.DefaultUser,  
  5.                 Password = ConnectionFactory.DefaultPass,  
  6.                 Port = AmqpTcpEndpoint.UseDefaultPort  
  7.             };  
Step 2
Alternatively we could also set the "Uri" property in the factory instance like this:
  1. var factory = new ConnectionFactory()  
  2.             {  
  3.                 Uri = new Uri("amqp://guest:guest@localhost:5672")  
  4.             };  
Although passing the credential in the Uri is not necessary since I am using the default credential, here I have kept it in the above example for illustration purposes.

Declare a Queue and Publish a Message

The below code snippet shows how we can create a connection with the factory, declare a queue, and then publish a message to the RabbitMQ container.
CONGIG_QUEUE is a class level const for my queue name.
  1. private const string CONFIG_QUEUE = "configQueue";
  1. using (var connection = factory.CreateConnection())  
  2.            using (var channel = connection.CreateModel())  
  3.            {  
  4.                channel.QueueDeclare(queue: CONFIG_QUEUE,  
  5.                                     durable: false,  
  6.                                     exclusive: false,  
  7.                                     autoDelete: false,  
  8.                                     arguments: null);  
  10.                string message = "CPUThreshold: " + config.CPUThreshold;  
  11.                var body = Encoding.UTF8.GetBytes(message);  
  13.                channel.BasicPublish(exchange: "",  
  14.                                     routingKey: CONFIG_QUEUE,  
  15.                                     basicProperties: null,  
  16.                                     body: body);  
  17.            }  
As you can see, I am using an imaginary CPUThreshold value that could be passed to the POST method in the controller through API call which I encode into a message and publish through a queue.
With this, we complete our implementation of the producer demo.
Next up, let's create a dummy Consumer and consume the message.

The Consumer

I have created the consumer here as a console application. As with the producer, here we start with establishing a connection to the running container queue. We must connect to the same queue as the producer to listen to the messages published. After that, we consume the message produced and send an "Ack" back. The message is removed from the queue.
The below code snippet is self-explanatory:
  1.             //establish connection  
  2.             var factory = new ConnectionFactory()  
  3.             {  
  4.                 Uri = new Uri("amqp://guest:guest@localhost:5672")  
  5.             };  
  6.             var rabbitMqConnection = factory.CreateConnection();  
  7.             var rabbitMqChannel = rabbitMqConnection.CreateModel();  
  9.             //declare the queue  
  10.             rabbitMqChannel.QueueDeclare(queue: CONFIG_QUEUE,  
  11.                                      durable: false,  
  12.                                      exclusive: false,  
  13.                                      autoDelete: false,  
  14.                                      arguments: null);  
  16.             rabbitMqChannel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);  
  18.             //consume the message received  
  19.             var consumer = new EventingBasicConsumer(rabbitMqChannel);  
  20.             consumer.Received += (model, args) =>  
  21.                 {  
  22.                     var body = args.Body;  
  23.                     var message = Encoding.UTF8.GetString(body.ToArray());  
  24.                     Console.WriteLine("CPUThreshold Changed To: " + message);  
  25.                     rabbitMqChannel.BasicAck(deliveryTag: args.DeliveryTag, multiple: false);  
  26.                     Thread.Sleep(1000);  
  27.                 };  
  28.             rabbitMqChannel.BasicConsume(queue: CONFIG_QUEUE,  
  29.                                          autoAck: false,  
  30.                                          consumer: consumer);  
  31.             Console.ReadLine();  
Let's see all these together!
Now that it's all developed and set up, let's see them working.

RabbitMQ Management

With the container set up and configured as per our first step, we should be able to login to the management console of RabbitMQ in this address: http://localhost:15672/
We would need to log in using the default credential as 'guest' for both the username and password.
After a successful login, we should be able to see that there are no queues presently created under the "Queue" tab.

Spin-up the Producer Web API & Send POST request through Postman

After we have successfully spun up our Producer Web API, we can send a POST call through Postman:
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client
With this successful POST call, if we go back and observe the management console, we will see a new queue been created under the "Queue" tab with the same name that we have specified in our code.
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client
Then if we see the queue, we will see one message has been successfully queued and is waiting to be consumed:
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client

Run the Consumer Console App

Now, we would run the consumer app to start listening to the queue in the RabbitMQ instance and start consuming the message. After successful execution of the console app, we should see the following output in the console window:
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client
This confirms the successful consumption of the message from the queue.
Let's summarize everything!
So, we first started up with pulling the RabbitMQ image from dockerhub and then containerizing it and mapping it to AMQP compatible port in the container host. After that, we ran the container and ensured it is running. We then developed dummy producer and consumer apps to establish a connection to the running container and produce and consume the messages through the open-source message broker instance hosted in the docker container.
Produce And Consume Messages To RabbitMQ Docker Container Using .NET Client
This is the full view where I have tried to show the management console, the Postman sending POST messages to trigger the message-publications and the consumer console app output. As you could see here, with repetitive messages being published with varying CPU Threshold values, the messages are published and consumed via the message broker instance which is running as a docker container.