Practical .NET
Offloading Work from Your Application with a Queue
Sometimes you can improve your application's response time by shunting some activities to offline processing -- sending an e-mail response, for instance. Here's how to use Microsoft Message Queue (available on both your development and production computers) to simplify the process.
You can always improve your application's response time by doing less. One way of doing less is to move tasks offline for processing later. "Later" in this case might mean "this evening" or even "five seconds later on another computer." The easiest way to do that is to have your application write the necessary information to a Microsoft Message Queue (MSMQ) and have another application read it and process the information. The queue can even be transactional; should you roll back the transaction, your MSMQ entries will also be backed out. In this column, I'm going to look at writing to the queue, along with how to create a queue from code to simplify deployment.
Adding MSMQ and Creating a Queue
MSMQ is an optional component for Windows. If MSMQ isn't installed, it's easy to add. In Windows Server 2008, start Server Manager, select Action | Add Feature. In the resulting dialog, check Message Queuing; In Windows 7, open Control Panel | Programs and Features, then select Turn Windows Features on or off. Once there, drill down before checking off MSMQ.
You can manage queues from within Visual Studio: Open Server Explorer, drill down through Servers and the name of your computer to get to Message Queues. To add a queue, right-click Private Queues and select Create Queue. To create a queue, you just need to supply a name. Optionally, you can check the Make queue transactional option to include your queue messages in transactions.
There is one problem with adding queues this way: You'll need to recreate the queue on your production computer when it comes time to deploy to production. A better strategy is to have your code check to see if the queue exists on the computer, and create the queue if necessary. Before working with queues, you'll need to add a reference to System.Messaging.
This code checks to see if a queue called "emails" exists in Private Queues, and if the queue doesn't exist, creates it. By passing True as the second parameter, this code makes the queue transactional:
If Not MessageQueue.Exists(".\private$\emails") Then
MessageQueue.Create(".\private$\Emails", True)
End If
In this code, the dot represents the local computer. Security permitting, you can replace that dot with the name of another computer on your network and read or write messages to that computer.
Once the queue's created, you can retrieve a reference to the queue by creating a MessageQueue object, passing the name of the queue:
Dim q As New MessageQueue(".\private$\emails")
Reading and Writing Messages
The next step is to create an MSMQ message and put your message into its Body property. You'll probably want to do more than that, though.
By default, MSMQ doesn't save any messages to your hard disk. Instead, to improve performance, messages are held in memory. However, that means should the computer crash, your messages will be lost. To prevent that, you should set the Recoverable property on the Message to True to ensure that the message is saved to disk. To make your life easier, you should also set the Label property on the Message because that property is displayed when viewing messages in Server Explorer -- you'll need that Label to find messages (and for that same reason, it should be a unique identifier).
Putting that all together, a bare-bones message might look like the following code:
Dim msg As New Message()
msg.Body = "Data"
msg.Recoverable = True
emm.Label = "New Message " & DateTime.Now.ToShortTimeString
q.Send(msg, MessageQueueTransactionType.Single)
Note that the second parameter passed to the Send method is required with a transactional queue.
Other properties let you encrypt the message and set related queues. For instance, you can also specify the dead-letter queue in which to put the message if it can't be delivered, or specify a queue to which an acknowledgement is to be sent when the object is read.
That code works fine as long as your Message Body property just holds text. A more typical solution requires writing multiple pieces of information. For instance, assuming that I want to send e-mail confirmation messages offline, I'd create a class to hold that information:
Public Class EmailMessage
Public Property ToAddress As String
Public Property Subject As String
Public Property Body As String
End Class
Before putting an object into the Body property, you must format it. You can do that in a number of ways; but because all I want to pass is the data in my object, I use the XMLMessageFormatter class. To create an XMLFormatter object, you need to pass it an array of Type objects created from the class (or classes) that you're formatting. Once you've created the formatter, you must put it in the Message's Formatter property. The code to create and initialize my EmailMessage object before putting it in a Message:
Dim emm As New EmailMessage
emm.ToAddress = "[email protected]"
emm.Subject = "Your email confirmation"
emm.Body = "Your purchase has been approved."
Dim msgType(0) As Type
msgType(0) = GetType(EmailMessage)
Dim msg As New Message()
msg.Formatter = New XmlMessageFormatter(msgType)
msg.Body = emm
Now that you have a message on the queue, you'll want to read it. I'll look at that in my next column, along with where you should put that code.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.