Messaging with RabbitMQ and .NET review part 3: the .NET client and some initial code

Introduction

In the previous post we installed the RabbitMq service on Windows. I think you’ll agree that it wasn’t a very complicated process. We then logged into the management GUI using the default “guest” administrator user. We finally looked at how to create users and virtual hosts. We said that a virtual host was a container or namespace to delimit groups of resources within RabbitMq, such as “sales” or “accounting”. We also created a new user called “accountant”.

In this post we’ll start working with RabbitMq in Visual Studio. We’ll in particular start exploring the RabbitMq .NET client library.

The RabbitMq .NET driver and some initial code

Open Visual Studio 2015 or an earlier version and create a new Console application. I’ll call my instance RabbitMqNetTests. Import the following NuGet package:

RabbitMq NuGet package for .NET

Add the following using statement to Program.cs:

using RabbitMQ.Client;

Let’s create a connection to the RabbitMQ server in Main. The ConnectionFactory object will help us build an IConnection object which represents a connection to the RabbitMq message broker. There are at least 2 ways to build the connection. One is to provide a connection URI. The following will connect to the RabbitMq server with the user “accountant”, password “accountant”, port 5672, virtual host “accounting” using the connection string kind of syntax:

ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.Uri = "amqp://accountant:accountant@localhost:5672/accounting";
IConnection connection = connectionFactory.CreateConnection();
Console.WriteLine(string.Concat( "Connection open: ", connection.IsOpen));

Note that although the management UI opens on port 15672 the correct port number for access in code is 5672 for regular connections. If you set up TLS for your RabbitMq instance then the correct port will be 5671.

You can test the above code in the Main method of the console app. If the code executes without exceptions and you see “Connection open: True” in the console window then the connection was made successfully. If you change the connection string to a bad port number, e.g. 5670 then you’ll get the following exception:

An unhandled exception of type ‘RabbitMQ.Client.Exceptions.BrokerUnreachableException’ occurred in RabbitMQ.Client.dll
Additional information: None of the specified endpoints were reachable

This message usually indicates a problem with the connection string.

Another way of connecting to the message broker is by building up the connection factory object bit by bit using its public properties as follows:

ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.Port = 5672;
connectionFactory.HostName = "localhost";
connectionFactory.UserName = "accountant";
connectionFactory.Password = "accountant";
connectionFactory.VirtualHost = "accounting";
IConnection connection = connectionFactory.CreateConnection();

Remember that the IConnection object is thread safe and can be shared among multiple applications.

The channel

We also mentioned that an application communicates with RabbitMq through a channel which exists within a connection. A connection can have multiple channels to enable parallel communication of multiple threads. Each thread should have its own channel.

For some reason the channel is called IModel in the .NET client. An IModel represents a channel to the AMQP server:

IModel model = connection.CreateModel();  

From IModel we can access methods to send and receive messages and much more. Here’s how to close a channel:

channel.Close();

There’s also a boolean property to check whether the channel has been closed:

bool channelIsClosed = channel.IsClosed;

Durability basics

There are two types of queues, exchanges and messages from a persistence point of view:

  • Durable: messages and other resources are saved to disk so they are available even after a server restart. There’s some overhead incurred while reading and saving messages. If durability is set to true then the resource will be persisted to disk and not only exist in memory. You’ll probably want to have the durable parameter set to true in a real life messaging scenario
  • Non-durable: the resources are persisted in memory only. They disappear after a server restart but offer a faster service

Keep these advantages and disadvantages in mind when you’re deciding which persistence strategy to go for.

The RabbitMq service is run by a Windows service visible in the Services window:

RabbitMq windows service

Later on you can always simulate a RabbitMq server restart by right-clicking on the above node and selecting the Restart option. All non-durable resources will be wiped out from memory but the durable ones will survive, including durable messages that have not yet been acknowledged by any client at the time of the server crash.

Creating queues, exchanges and bindings at runtime

The IModel object will let us create all the necessary resources for exchanging messages. The ExchangeDeclare method will create an exchange. The following is an example for the exchange type direct. The exchange name is the first parameter:

channel.ExchangeDeclare("my.first.exchange", ExchangeType.Direct);

The above is the most basic overload of ExchangeDeclare. Here is a the longest overload:

channel.ExchangeDeclare("my.first.exchange", ExchangeType.Direct, true, false, null);

The third parameter, i.e. “true” sets durability to true. Then the “false” stands for the auto-delete option. Setting auto-delete to true for exchanges and queues means that they are automatically deleted as soon as they are no longer used, i.e. there’s no channel using them. The last parameter is a dictionary object where we can pass in some further options. We won’t worry about that right now so we set it to null.

Next we create a queue using the QueueDeclare function:

channel.QueueDeclare("my.first.queue", true, false, false, null);

We have the following parameters to the function:

  • The queue name
  • Whether it is durable
  • Whether it is exclusive, which means whether the queue is exclusively used for the connection
  • Whether it should be auto-deleted
  • The same dictionary object with custom options as in ExchangeDeclare

The last piece that’s missing is the binding. We use the QueueBind method for that:

channel.QueueBind("my.first.queue", "my.first.exchange", "");

Here we have the queue name, the exchange name and then the routing key. We set the routing key to an empty string to indicate that we don’t need any.

Alright, let’s put all of this together in the Main method of the console application:

static void Main(string[] args)
{
	ConnectionFactory connectionFactory = new ConnectionFactory();
			
	connectionFactory.Port = 5672;
	connectionFactory.HostName = "localhost";
	connectionFactory.UserName = "accountant";
	connectionFactory.Password = "accountant";
	connectionFactory.VirtualHost = "accounting";

	IConnection connection = connectionFactory.CreateConnection();
	IModel channel = connection.CreateModel();
	Console.WriteLine(string.Concat( "Connection open: ", connection.IsOpen));

	channel.ExchangeDeclare("my.first.exchange", ExchangeType.Direct, true, false, null);
	channel.QueueDeclare("my.first.queue", true, false, false, null);
	channel.QueueBind("my.first.queue", "my.first.exchange", "");

	channel.Close();
        connection.Close();
	Console.WriteLine(string.Concat("Channel is closed: ", channel.IsClosed));

	Console.WriteLine("Main done...");
	Console.ReadKey();
}

Run the above code. You shouldn’t get any exceptions thrown. Let’s check whether the resources are visible in the management UI:

First RabbitMq queue created in code

First RabbitMq exchange created in code

If you click on the exchange name you can also view the existing bindings:

RabbitMq binding created in code

The code seems to have worked well. The great thing is that you can run this code as often you want. If the declared resource already exists then the code will not do anything, i.e. you won’t get an exception if you declare the same queue twice or more often.

You can also restart the RabbitMq service in the Services window to simulate a server restart. The resources should still be there.

Publishing a message

The last bit we’ll look at in this post is how to publish a message to our newly created queue. Add the following code after the QueueBind method:

IBasicProperties properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.ContentType = "text/plain";
PublicationAddress address = new PublicationAddress(ExchangeType.Direct, "my.first.exchange", "");
channel.BasicPublish(address, properties, Encoding.UTF8.GetBytes("This is a message from the RabbitMq .NET driver"));

IBasicProperties lets us set a wide range of properties for the message. Here we only use two of them. We want the message to be durable, i.e. persistent. We also declare the MIME type. We then build a PublicationAddress object with the exchange name, the exchange type and the empty routing key. We finally publish a message which must first be transformed into a byte array.

Run the above code. If everything went as expected then there should be a message in the queue:

First message registered in RabbitMq queue

Before we do anything else we can again simulate a server crash and restart the RabbitMq service like we did above. The message should still be there. We can retrieve the message in the UI in the “Get messages” section of the queue. Click on the queue name and get the message by pressing the get messages button:

Message retrieved from the RabbitMq queue

That’s enough for now. We’ll continue our exploration in the next post by looking at how to get messages in code.

View the list of posts on Messaging here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

6 Responses to Messaging with RabbitMQ and .NET review part 3: the .NET client and some initial code

  1. ramessesx says:

    Hi Andras, thanks again for posting. I got everything to work with the ‘/’ virtual host, but not with the new Vitual Host (accounting), do you know of any reasons why this is ?

    • Andras Nemes says:

      Hello, what kind of exception do you get? Does the virtual host really exist? //Andras

      • ramessesx says:

        Hi Andras. I was not getting any exceptions as such, I simply was not seeing the newly created exchange or queue on the RabbitMQ Managment console, but I have just figured out what the problem was => Permissions.

        I was signed in as ‘guest’, not ‘accountant’, and as such it would not allow me to view the exchanges or queues for the ‘accounting’ virtual host. After setting permission for ‘guest’ on the ‘accounting’ host, I was able to view everything.

        Sorry for troubling you with such a ‘vague’ question, and thanks for taking the time to respond.

        Love the blog, look forward to more …

      • Andras Nemes says:

        Hello, it’s ok, don’t worry, I’m glad you find this blog useful. //Andras

  2. ict22 says:

    Hi Andras, many thanks for the update. Great and very useful blog!

  3. Great series! Thank you for explaining in such detail!

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.