To send a message, you use the MessageQueue.Send method. This method accepts the object you want to send and the label (title) of the message. Listing 8-8 shows an example. Listing 8-8 Sending a simple text message in XMLDim Queue As MessageQueue If MessageQueue.Exists(".\Private$\TestQueue") Then Queue = New MessageQueue(".\Private$\TestQueue") Else Queue = MessageQueue.Create(".\Private$\TestQueue") End If Queue.Send("Inventory Request Order Item #222", "Test Message") Figure 8-5 shows the retrieved message. Figure 8-5. Receiving a message
By default, the message is serialized to an XML format. Figure 8-6 shows the message body in the Computer Management tool. Figure 8-6. The XML-formatted message body
Alternatively, you can set the MessageQueue.Formatter property to an instance of the BinaryMessageFormatter, which ensures a more compact message (and thereby lessens network traffic). (See Listing 8-9.) Listing 8-9 Sending a simple text message in binaryDim Queue As MessageQueue If MessageQueue.Exists(".\Private$\TestQueue") Then Queue = New MessageQueue(".\Private$\TestQueue") Else Queue = MessageQueue.Create(".\Private$\TestQueue") End If Queue.Formatter = New BinaryMessageFormatter() Queue.Send("Inventory Request Order Item #222", "Test Message") Figure 8-7 shows the same message in a binary format. The size has dwindled from 73 to 57 bytes. Figure 8-7. The binary-formatted message body
Sending Object MessagesOf course, message queues would be fairly awkward if they accepted only text messages. Instead, you can submit any type of object as long as it satisfies three criteria:
Listing 8-10 shows an ideal Message class. It encapsulates information about an order, which the message recipient can then process. Listing 8-10 A serializable message class<Serializable()> _ Public Class Order Public OrderCode As String Public ClientName As String Public Items As ArrayList Public Sub New() ' No actions are required in the default constructor. End Sub Public Sub New(ByVal code As String, ByVal client As String) OrderCode = code ClientName = client End Sub End Class The code to send this custom object is trivially easy (as shown in Listing 8-11). Listing 8-11 Sending an object messageDim Ord As New Order("283c74c3-721d-4f34-80e5-57657b6cbc27", _ "Smith & Smith") Queue.Send(Ord, "Test Order") Note When you use objects in a message queue, be sure to clearly identify the object type before creating the queue. Trying to determine what type of object has been received can be a nightmare for the server-side application. It's far easier if a queue is restricted to one type of object. However, the server application will always need to check the object type when retrieving a message, or at least use error-handling code for its casting operation. This is because there is no way to enforce queue type safety and restrict clients from submitting incorrect objects in a message. You will also run into problems if clients send the same type of object but encode it with the wrong formatter. Advanced Message ConfigurationNone of these examples shows you how to send a message with encryption, a high priority, or a customized timeout. All these options, and more, are available through the Message class. To use them, just create a Message instance and pass the custom object that you want to send in the Message constructor. Then send the message object using the MessageQueue.Send method. This two-step process looks like Listing 8-12. Listing 8-12 Sending a configured message with the message classDim Ord As New Order("283c74c3-721d-4f34-80e5-57657b6cbc27", _ "Smith & Smith") ' Create the message wrapper. Dim OrderMessage As New Message(Ord) ' Enable encryption (requires access to Active Directory). OrderMessage.UseEncryption = True OrderMessage.EncryptionAlgorithm = Messaging.EncryptionAlgorithm.Rc2 ' Store the outgoing message in a file before sending it. OrderMessage.Recoverable = True ' Make it high priority. OrderMessage.Priority = Messaging.MessagePriority.VeryHigh ' Give it an hour to make it to the destination queue. OrderMessage.TimeToBeReceived = TimeSpan.FromHours(1) ' Store a copy of the outgoing message on this computer. OrderMessage.UseJournalQueue = True Queue.Send(OrderMessage, "Encrypted Order") The Message class doesn't provide any methods, but it does contain a slew of properties you can set. The reasons why you might create a Message wrapper include the following:
Table 8-4 lists some key properties and the defaults that are used if you don't modify the Message class.
Receiving a MessageTypically, a server-side application uses the Receive method to read a single message from the queue. For a nontransactional queue, this is the message with the highest priority. If multiple messages exist with the same priority, the oldest one is read first. For a transactional queue, the message received first is simply the message that was sent first. Before you can receive a message, you need to create a formatter that is configured to recognize specific .NET types. You identify the allowed types using an array of Type objects. In the following example, the code is searching for Order types in the message body: ' Set the allowed types. Dim AllowedTypes() As Type = {GetType(Order)} Queue.Formatter = New XmlMessageFormatter(AllowedTypes) Note, however, that this code won't prevent you from attempting to download a message of another type, at which point an exception will be generated. A server-side application might use this type of logic to receive all the messages in the queue (as shown in Listing 8-13). Listing 8-13 Receiving all messagesDim Queue As New MessageQueue(".\Private$\TestQueue") Dim ReceivedMessage As Message ' Set the allowed types. Dim AllowedTypes() As Type = {GetType(Order)} Queue.Formatter = New XmlMessageFormatter(AllowedTypes) Do ReceivedMessage = Queue.Receive() ' Check if it is the expected type. If TypeOf ReceivedMessage.Body Is Order Then ' Process message here. Dim ReceivedOrder As Order = CType(ReceivedMessage.Body, Order) Console.WriteLine(ReceivedOrder.OrderCode) End If Loop If there is no message in the queue when you call Queue.Receive, your code just stops, waiting indefinitely for the next message to be received. Alternatively, you can use an overloaded version of the Receive method that accepts a TimeSpan. If no message is received after the specified amount of time, the code continues. This enables you to create a component that can intelligently check whether it should shut down (perhaps in response to a user interface action or a signal set by another component). Without using a TimeSpan, the code could block perpetually and remain unresponsive (as shown in Listing 8-14). Listing 8-14 Polling for messagesDo ReceivedMessage = Queue.Receive(TimeSpan.FromSeconds(60)) ' Check if a message was received. If ReceivedMessage = Nothing Then ' Yield the processor for some other work, unless ' a stop is requested. If Not Me.ShouldStop Then Thread.Sleep(TimeSpan.FromSeconds(60)) End If Else ' (Process message here.) End If ' Check for a requested shutdown. If Me.ShouldStop Then Exit Loop Loop The MessageQueue class also supports asynchronous reads through its BeginReceive and EndReceive methods. These work the same as the asynchronous methods examined in Chapter 6 and are useful when receiving large data objects.
Browsing MessagesWhen a message is received, it is removed from the queue, even if your application does not use it. To retrieve a copy of the first message but leave the message in the queue, you can use the MessageQueue.Peek method. However, this technique is inconvenient because if called multiple times, you will always receive the same message. A better approach is the For Each syntax, which enables you to walk through all the messages without removing them. Listing 8-15 shows an example that iterates through all the messages and displays the title of each one in a list control. Listing 8-15 Reading messages without receiving themDim Queue As New MessageQueue(".\Private$\TestQueue") Dim Message As Message ' Configure the list to show the message title. lstMessages.DisplayMember = "Label" For Each Message In Queue lstMessages.Items.Add(Message) Next Note that the whole object is stored in the list control. This enables you to retrieve more message information after the user makes a selection. Or, the user can choose a message, and you can use the MessageQueue.ReceiveById method to download and remove the selected message, regardless of where it is positioned in the queue. Response and Acknowledgement MessagesConceptually, there are two types of message acknowledgements: automatic and manual. Automatic acknowledgements are sent to a dedicated administration queue. The Message Queuing service sends the acknowledgement message to indicate that a message was received or processed, without requiring the receiving application to take any action (as shown in Listing 8-16). Listing 8-16 Requesting an acknowledgement messageDim Queue As New MessageQueue(".\Private$\TestQueue") Dim Ord As New Order("283c74c3-721d-4f34-80e5-57657b6cbc27", _ "Smith & Smith") Dim OrderMessage As New Message(Ord) OrderMessage.AdministrationQueue = New _ MessageQueue(".\Private$\TestAcknowledgement") OrderMessage.AcknowledgementType = AcknowledgementTypes.PositiveArrival Queue.Send(OrderMessage, "Order") ' Hold on to the correlation ID. ' It's required in order to check for an acknowledgement. Dim CorrelationID As String = OrderMessage.Id Here's how it works: Before you send the message, you set the Message.AdministrationQueue property to the administration queue that will receive the message. Then you indicate the desired type of acknowledgement by setting Message.AcknowledgementType to one of the values listed in Table 8-5.
After sending the message, you should examine and record the Message.Id property (perhaps using a local XML file or text file). You can then retrieve the corresponding acknowledgement message from the administration queue, when it arrives, by using the MessageQueue.ReceiveByCorrelationId method and specifying the ID of the message you sent (as shown in Listing 8-17). Acknowledgement messages don't have any interesting content. However, you can examine the Message.Acknowledgement property of an acknowledgement message to determine whether the message was received, timed out, and so on. Listing 8-17 Retrieving an acknowledgement messageDim AcknowledgementMessage As Message AcknowledgementMessage = Queue.ReceiveByCorrelationId(CorrelationId) If AcknowledgementMessage.Acknowledgement = Acknowledgement.ReachQueue ' (The order reached the queue. It may or may not have been ' processed.) End If Response queues operate under a similar principle, but they require the receiving application to take some action. They can also contain additional information. For example, a response message to an order might be a custom structure with information about the order date and shipping charges. To use a response queue, the sending application creates a MessageQueue object that identifies the response queue (which is an ordinary queue) and sets it in the Message.ResponseQueue property (as shown in Listing 8-18). Listing 8-18 Identifying a response messageDim Queue As New MessageQueue(".\Private$\TestQueue") Dim Ord As New Order("283c74c3-721d-4f34-80e5-57657b6cbc27", _ "Smith & Smith") Dim OrderMessage As New Message(Ord) ' Specify a response queue. Dim ResponseQueue As New MessageQueue(".\Private$\TestQueueResponse") OrderMessage.ResponseQueue = ResponseQueue Queue.Send(OrderMessage, "Order") The receiving application must then examine this property, create a new message, and set the Message.CorrelationId property of the response message to match the Message.Id of the received message. This allows the client to determine what the message is responding to. Unlike with acknowledgement messages, it's the recipient's responsibility to perform these tasks. The Message Queuing infrastructure doesn't send a response message automatically. |