Working with .NET Messaging Services


In the .NET Framework, messaging services reside within the System.Messaging namespace. Three fundamental classes accomplish most messaging functionality: Message, MessageQueue, and MessageQueueException.

The Message class abstracts individual messages and enables you to specify properties such as label, body, priority, authentication, and so on. This class also offers methods supporting data encryption, tracing, and expirations.

The MessageQueue class abstracts an individual message queue and enables you to create and delete queues. You can also use this class to send simple and complex messages, delete all messages from the message queue, read posted messages, or peek into the queue to find a specific message.

The MessageQueueException class provides detailed information about exceptions thrown while attempting messaging- related activities. When any messaging task fails to succeed, this exception is thrown, and it provides a description of the problem, an error code, and stack trace information.

Creating a Messaging Framework

A messaging framework can bring consistency to how queues and messages are managed within an application. The enterprise application and the extended distributed service can use the framework.

Begin by selecting the SystemFrameworks project within the IssueTracker solution. Next , it is important to add a project-level reference to the messaging namespace within Visual Studio .NET. From the menu, select Project ˜ Add Reference. Then, select the System.Messaging.dll component, as shown in Figure 4-2.

click to expand
Figure 4-2: Selecting the project reference in Visual Studio .NET

The MessagingFramework object provides a uniform method for managing message queues and for managing the sending and receiving of messages. Both the application and distributed services connected to the application use this object by means of messaging. Listing 4-1 outlines the basic MessagingFramework object code.

Listing 4-1: The MessagingFramework Class
start example
 public class MessagingFramework {     private string _ProcessName = "";     private string _QueueInboxName = "";     private string _QueueOutboxName = "";     private string _QueueErrorsName = "";     public enum QueueType     {         Inbox = 1,         Outbox = 2,         Errors = 3     }     public MessagingFramework()     {         _ProcessName = "  Undefined  ";         _QueueInboxName = ".\private$\" +_ProcessName + "Inbox";         _QueueOutboxName = ".\private$\" + _ProcessName + "Outbox";         _QueueErrorsName = ".\private$\" + _ProcessName + "Errors";         return;     }     public MessagingFramework( string strProcessName )     {         _ProcessName = strProcessName;         _QueueInboxName = ".\private$\" + _ProcessName + "Inbox";         _QueueOutboxName = ".\private$\" + _ProcessName + "Outbox";         _QueueErrorsName = ".\private$\" + _ProcessName + "Errors";         return;     }     public string ProcessName     {         get         {             return _ProcessName;         }         set         {             _ProcessName = value;             _QueueInboxName = ".\private$\" + _ProcessName + "Inbox";             _QueueOutboxName = ".\private$\" + _ProcessName + "Outbox";             _QueueErrorsName = ".\private$\" + _ProcessName + "Errors";         }     } } 
end example
 

The first element of the path identifies the target server. Because you are creating a message queue locally, you use a dot in place of a server name . The next element designates which type of queue to create. To create a private message queue, you use the private$ identifier. To create a public message queue, the path appears the same but without the private$ identifier.

Within this application framework, each messaging client should keep a single instance of the MessagingFramework class. MessagingFramework maintains the fully qualified name of Inbox, Outbox, and Errors message queues as well as the name of the process (or client) managing it. The code also defines an enumerated type that identifies the three application message queue types. All variables are set within the two available constructors or within an accessor method.

Creating Message Queues

Although creating a message queue using the Computer Management console seems easy enough, you cannot expect the average enterprise customer to handle it. To create a message queue programmatically, as in an installation program, only a single method call to the MessageQueue object is necessary. Listing 4-2 demonstrates how the Create method creates public and private message queues.

Listing 4-2: Creating a New Message Queue
start example
 public void CreateQueues() {     try     {          //create the inbox queue          if( ! MessageQueue.Exists( _QueueInboxName ) )              MessageQueue.Create( _QueueInboxName );          //create the outbox queue          if( ! MessageQueue.Exists( _QueueOutboxName ) )             MessageQueue.Create( _QueueOutboxName );          //create the errors queue          if( ! MessageQueue.Exists( _QueueErrorsName ) )              MessageQueue.Create( _QueueErrorsName );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return; } 
end example
 

The Create method creates three new message queues. Before the application creates each queue, it checks to see that they do not already exist. This code catches any exceptions that are thrown and writes them to the Windows Event Viewer.

Deleting Message Queues

You can also delete message queues programmatically. To delete a message queue, as in an uninstall program, only a single method call to the MessageQueue object is necessary. Listing 4-3 demonstrates how the Delete method removes all three application message queues.

Listing 4-3: Deleting an Existing Message Queue
start example
 public void DeleteQueues() {     try     {          //delete the inbox queue          if( ! MessageQueue.Exists( _QueueInboxName ) )              MessageQueue.Delete( _QueueInboxName );          //delete the outbox queue          if( ! MessageQueue.Exists( _QueueOutboxName ) )              MessageQueue.Delete( _QueueOutboxName );          //delete the errors queue          if( ! MessageQueue.Exists( _QueueErrorsName ) )              MessageQueue.Delete( _QueueErrorsName );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return; } 
end example
 

The Delete method functions similarly to the Create method. It deletes all three message queues after checking to ensure they actually exist. If permissions do not allow the deletion of a queue or the queue does not exist, then a MessageQueueException exception will be thrown. Although it is permissible to delete a message queue while unread messages are still present, it is much cleaner to purge a queue first.

Sending Simple Messages

You can send simple messages from one process to another with the help of the System.Messaging namespace objects. Essentially , an application only needs to connect to a message queue ( assuming that the queue already exists), specify a message label and body, and then send the message. Listing 4-4 implements the SendMessage method within the messaging framework. An application uses this method to specify a target process, a subject, and a message body.

Listing 4-4: MessageComposer Test Application
start example
 public void SendMessage( string strTargetProcess, string strSubject,     string strBody ) {     MessageQueue queue = null;     string strTargetQueueName;     try     {          strTargetQueueName = ".\private$\" + strTargetProcess + "Inbox";          if( MessageQueue.Exists( strTargetQueueName ) )          {             queue = new MessageQueue( strTargetQueueName );             queue.Send( strSubject, strBody );          }     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return; } 
end example
 

Listing 4-4 begins by declaring working variables. Next, it specifies the fully qualified path to the destination Inbox message queue. The code then checks the target message queue to ensure it exists. If the queue exists, then the method connects to the queue and sends the message using the subject and body values supplied to the method. If the named message queue does not exist or the message fails to send, an exception is thrown. The exception handler traps all MessageQueueExceptions and writes the message to the Windows Event Viewer. Otherwise, the message is sent successfully. From the Computer Management console, you can view the list of posted messages within the IssueTracker private message queue. The message remains in the queue until it is read.

Receiving Simple Messages

Receiving simple messages from another process is also consistently packaged into the application messaging framework. Essentially, the messaging framework points to the same message queue to receive the message. Listing 4-5 implements the ReceiveMessage method to query a message queue for a new message.

Listing 4-5: MessageConsole Test Application
start example
 public Message ReceiveMessage() {     MessageQueue queue = null;     Message message = null;     try     {         MessageQueue queue = new MessageQueue( _QueueInboxName );         //retrieve message from the queue         message = queue.Receive();     }     catch( Exception exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return message; } 
end example
 

Listing 4-5 begins by establishing a connection to the object owner's Inbox message queue. Next, the queue's Receive method returns a single message. The message returned is the latest message entered into the queue. If any exceptions are thrown, they are handled and written to the Windows Event Viewer. In any case, the queue closes and the received message returns to the calling process.

Peeking into Messages

Each time the Receive method is invoked, the received message is permanently removed from the message queue. The Peek method lets you preview the message labels and decide if you want to read it. This is helpful because a message within a shared queue might be intended for another application and removing it from the queue could cause problems. Listing 4-6 demonstrates how you change the application messaging framework to peek at message labels rather than receive the entire message.

Listing 4-6: MessageConsole Application with Message Peeking
start example
 public Message PeekMessages( string strLookFor ) {     MessageQueue queue = null;     Message message = null;     try     {         MessageQueue queue = new MessageQueue( _QueueInboxName );         message = queue.Peek();         string[] types = { "System.String" };         message.Formatter = new XmlMessageFormatter( types );         if( message.Label.IndexOf( strLookFor ) >= 0 )             return message;     }     catch( Exception exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return message; } 
end example
 

The method begins by invoking the Peek method and assigning its results to a new Message object. Next, a string array specifies the target data type as a String. The method also defines a message formatter to be applied to the message queue. Regardless of what content is entered for the body, its internal representation is persistent in Extensible Markup Language (XML). Because the message body is natively persistent in XML, it is necessary to apply a message formatter to convert the body into a readable message. The XmlMessageFormatter class offers services that serialize and deserialize objects from the body of a message. To use this class, the target type needs to tell the formatter what schemas to attempt to match when deserializing a message. In this case, set the target type to System.String, instructing the formatter to convert the body of a message into a readable string.

After the message label has been read and the data has been converted, regular expressions can be applied to the message. The method searches for a match to the string parameter provided. If there is a match, the entire message is returned to the calling process. The problem with the Peek method is that it only sees the first message in the queue; some applications might find this limiting. Fortunately, there are other methods that enable an application to view multiple messages within a queue.

Receiving Multiple Messages

You can retrieve a dynamic list of messages using the MessageEnumerator. The messages within the enumerator appear in the same order as within the queue: by message priority. The enumerator serves as a cursor and allows an application to advance from the first message in the queue to the next by invoking the MoveNext method. The enumerator is only able to advance and cannot step backward through the messages. If a new message is added to the queue with a higher priority than the message being read, then it will not be accessed. To return to the top of the queue, invoke the Reset method. Listing 4-7 shows how to display multiple messages within a message queue.

Listing 4-7: Displaying Multiple Messages Within a Message Queue
start example
 public MessageEnumerator ReceiveAllMessages() {     MessageQueue queue = null;     MessageEnumerator enumerator = null;     try     {         queue = new MessageQueue( _QueueInboxName );         //retrieve all messages from the queue         enumerator = (MessageEnumerator)(queue.GetEnumerator());     }     catch( Exception exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return enumerator; } 
end example
 

In Listing 4-7, the GetEnumerator method retrieves a dynamic list of the messages within the queue. Like a DataReader object, MessageEnumerator advances through the list of messages in a forward-only read by invoking the MoveNext method.

Note  

If the message queue is instantiated with the DenyShareReceived attribute set to true, then no other applications can modify messages within the enumerator while the connection is open .

Understanding Additional Message Queue Interaction

In addition to basic message queue manipulation such as creating and deleting message queues, a number of other method functionality is available. Such functionality includes retrieving a list of all available queues, searching for a specific queue, and purging all messages within a queue.

Listing Available Queues

Enterprise applications that implement a messaging framework often interact with multiple message queues on multiple computers. There are several occasions when it is necessary to build a list of available message queues. This often occurs in a messaging management interface where the system administrator configuring your enterprise application can create, modify, and delete message queues. Listing 4-8 presents an additional method that lists all available message queues.

Listing 4-8: Outputting All Available Message Queues
start example
 public ArrayList ListAllQueues() {     ArrayList arrayQueues = new ArrayList();     try     {         arrayQueues.AddRange( MessageQueue.GetPrivateQueuesByMachine( "." ) );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return arrayQueues; } 
end example
 

In Listing 4-8, the GetPrivateQueuesByMachine method lists all private message queues on the local computer. To list all available public message queues, substitute GetPublicQueuesByMachine for this method. You can also specify the computer name to search for all message queues on that computer. The resulting list is stored in an ArrayList object for easier iteration within the application.

Searching for a Queue

In enterprise environments, it is easy to end up with multiple message queues processing information from different systems. Returning a list of all available message queues is helpful, but being able to search for a specific message queue is invaluable. Listing 4-9 demonstrates how to define search criteria to return a list of message queues that match the specified criteria.

Listing 4-9: Specifying a Message Queue by Criteria
start example
 public ArrayList ListApplicationQueues() {     ArrayList arrayQueues = new ArrayList();     try     {         arrayQueues.AddRange(             MessageQueue.GetPublicQueuesByLabel( _QueueInboxName ) );         arrayQueues.AddRange(             MessageQueue.GetPublicQueuesByLabel( _QueueOutboxName ) );         arrayQueues.AddRange(             MessageQueue.GetPublicQueuesByLabel( _QueueErrorsName ) );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return arrayQueues; } 
end example
 

Listing 4-9 creates an array of message queues that is populated by the GetQueuesByLabel method. It passes a string argument to this method to filter the list of message queues down to those whose label matches. In addition to filtering message queues by label, you can use other methods to filter by category or machine name.

Determining If a Queue Exists

Applications can easily determine if a specific message queue exists. The MessageQueue class provides a test method called Exists that takes a parameter indicating the qualified path to the message queue. Listing 4-10 checks to see if a specific message queue exists before attempting to delete it.

Listing 4-10: Checking to See If a Message Queue Exists
start example
 public void DeleteQueues() {     try     {          //delete the inbox queue          if( ! MessageQueue.Exists( _QueueInboxName ) )              MessageQueue.Delete( _QueueInboxName );          //delete the outbox queue          if( ! MessageQueue.Exists( _QueueOutboxName ) )              MessageQueue.Delete( _QueueOutboxName );          //delete the errors queue          if( ! MessageQueue.Exists( _QueueErrorsName ) )              MessageQueue.Delete( _QueueErrorsName );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return; } 
end example
 

Purging a Queue

It is not often that you want to completely clear out a message queue. However, if you need to empty a message queue, the MessageQueue class provides the Purge method. This method internally sets the queue modification flag, which in turn updates its LastModifyTime property. Listing 4-11 defines a simple method that clears out all messages within the specified application message queue.

Listing 4-11: Purging All Messages from a Queue
start example
 public void EmptyQueue( MessagingFramework.QueueType queueType ) {     MessageQueue queue = null;     try     {         switch( queueType )         {             case QueueType.Inbox:                 queue = new MessageQueue( _QueueInboxName );                 break;             case QueueType.Outbox:                 queue = new MessageQueue( _QueueOutboxName );                 break;             case QueueType.Errors:                 queue = new MessageQueue( _QueueErrorsName );                 break;         }         queue.Purge();     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return; } 
end example
 

The Purge method deletes all messages within the specified queue without sending a copy of the message to the dead-letter queue or the journal queue. Once purged, the messages cannot be recovered.

Returning Failed Message Acknowledgments

You can enable message queues to return acknowledgment messages that indicate whether a message was successfully delivered. There are two types of acknowledgments. Positive acknowledgment messages indicate that a message was delivered to its destination message queue. Negative acknowledgment messages indicate that a message failed to reach its destination message queue. Both acknowledgments are system-generated messages sent from the original destination queue to a specially designated administration queue. Aside from being system generated, the most significant difference from other messages is that acknowledgment messages have no message body; instead, they have only a message label. Otherwise, acknowledgment messages are read from the administrative message queue just like any other message. Listing 4-12 demonstrates how you can designate an administrative message queue to receive negative acknowledgments when a message expires .

Listing 4-12: Designating an Administration Queue for Acknowledgment Messages
start example
 public void SendMessage( string strTargetProcess, string strSubject,     string strBody, int intMaxWaitSeconds ) {     string strTargetQueueName;     string strTargetErrorsQueueName;     try     {         strTargetQueueName = ".\private$\" + strTargetProcess + "Inbox";         strTargetErrorsQueueName = ".\private$\" + strTargetProcess + "Errors";         if( MessageQueue.Exists( strTargetQueueName ) )         {            MessageQueue queue = new MessageQueue( strTargetQueueName );            MessageQueue queueErrors =                new MessageQueue( strTargetErrorsQueueName );            Message message = new Message();            message.Label = strSubject;            message.Body = strBody;            message.TimeToBeReceived = new TimeSpan( 0, 0, intMaxWaitSeconds );            message.AdministrationQueue = queueErrors;            message.AcknowledgeType = AcknowledgeTypes.NegativeReceive;            queue.Send( message );         }     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     return; } 
end example
 

Listing 4-12 designates an administration queue to receive a negative acknowledgment message if the outgoing message fails to reach its destination. First, two message queue objects are instantiated: the original destination queue where the outgoing message is posted to and an administration message queue where system-generated acknowledgment messages appear. Next, you create the outgoing message. Set the AdministrationQueue property to point to your administration queue. Set the AcknowledgementType property to NegativeReceive to indicate that you are only interested in receiving negative acknowledgment messages. The TimeToBeReceived property identifies the expiration time of the outgoing message. With the help of the TimeSpan class, you can force the outgoing message to expire after 15 seconds. When your method is invoked, a message with the label "Test Message Label" appears in the IssueTracker message queue. After 15 seconds, the message expires and is removed from the Inbox queue, and an acknowledgment message is sent to the Errors administration queue. Additionally, the acknowledgment message includes a class description stating , "The time-to-be-received has elapsed."

Resending Failed Messages

Acknowledgment messages and administration queues can work together to resend messages that have failed to reach their destinations. Listing 4-13 presents an approach to reading messages stored in the administration queue and resending them to their intended destinations.

Listing 4-13: Resending Failed Messages
start example
 public void ResendMessage() {     MessageQueue queueErrors = null;     MessageQueue queueDestination = null;     ArrayList messagesArray = new ArrayList();     try     {         queueErrors = new MessageQueue( _QueueErrorsName );         messagesArray.AddRange( queueErrors.GetAllMessages() );         foreach( Message message in messagesArray )         {              queueDestination = message.ResponseQueue;              queueDestination.Send( message );         }         queueErrors.Purge();     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queueErrors.Close();         queueDestination.Close();     }     return; } 
end example
 

In Listing 4-13, any messages that expire and trigger an acknowledgment message will be resent. The listing begins by specifying the administration message queue as IssueTrackerErrors. Next, your method reads the administration queue like any other message queue ”by invoking GetAllMessages. Next, you loop through the messages within the queue. For each acknowledgment message within the queue, you set its new destination queue to the original message's destination queue. You can do this by assigning the destination queue to the acknowledgment message's ResponseQueue property. With the destination queue changed, the message can be resent to its original destination with one significant difference. The TimeToBeReceived property has been lost. The expiration property is gone because you are now working with the acknowledgment message, not the original. Unfortunately, the acknowledgment message maintains a limited subset of the original message's properties. To work with a true copy of the message, you need to work with the system journal queue.

Working with the Journal Message Queue

Resending messages from an administration queue works, but it certainly has some limitations. The problem is that the acknowledgment message that appears in the administration queue is not a true copy of the original message sent. Journal message queues actually store copies of the original outgoing message. Journal queues are meant to store critical messages that might need to be resent if they fail to reach their destinations.

Journal messages work well with acknowledgment messages. In addition to including the reason that an outgoing message has failed, acknowledgment messages also contain a CorrelationId. This property maps to the original message copied to the journal message queue. When an acknowledgment message comes in, you can match the CorrelationId to the original message and resend the original message.

By default, outgoing messages are not copied to the journal queue. Both messages and message queue objects have a UseJournalQueue property. When this property is true for a message queue, all messages tied to that queue are copied to the journal queue. When this property is true for an individual message, then only the specified message is copied. Typically, only transactional messages are important enough to copy into the journal queue. If a transactional message fails, then a number of other related business functions might be impacted. Working with the acknowledgment messages and the journal queue, the original transactional message can be resent quickly and accurately.

Note  

Journal queues have a maximum capacity determined by available disk space.When this capacity is reached, new messages are not copied and no notifications to the application are made. Thus, it is important to periodically purge the journal queue.

Understanding Message Serialization

Message serialization is the process of breaking down objects into a portable state that can be transported from one message queue to another. When objects are serialized, essentially their data members are stored and transported. The receiving component deserializes an object by reading the saved state information and instantiating the same object with the saved state. Figure 4-3 illustrates the message serialization and deserialization process.

click to expand
Figure 4-3: The message serialization/deserialization process

The function of serializing and deserializing an object is the responsibility of a message formatter. When a data object is sent to a message queue, the message formatter serializes the object into a stream. When that message is later read from the message queue, another message formatter deserializes the stream into the Body property of a Message object. There are three different message formatters available with Visual Studio .NET: XmlMessageFormatter, BinaryMessageFormatter, and ActiveXMessageFormatter.

XmlMessageFormatter is the default formatter used for all messages. Data objects and primitive data types are structured as a collection of humanreadable XML tags and attributes. Without any extra coding, any message will be serialized using this formatter when a MessageQueue object's Send method is invoked.

The BinaryMessageFormatter and ActiveXMessageFormatter formatters both convert data objects and primitive data types into a non- human-readable binary stream. ActiveXMessageFormatter is used specifically for compatibility with the MSMQ ActiveX component typically found in earlier Visual Basic applications.

For a complex data object to be properly serialized by BinaryMessageFormatter, the object's class declaration must apply the Serializable attribute. For example, to stream the Issue business object defined in Chapter 2, "Accessing Data in ADO.NET," modify the class declaration and its abstract base class definition to include the Serializable attribute:

 [Serializable] public class Issue : BusinessObject 

With a serializable object available to stream, modify the SendMessage method to use BinaryMessageFormatter, rather than its default XmlMessageFormatter. Begin by instantiating the BinaryMessageFormatter object. Next, assign the object to the Formatter property of the Message object. Set the desired data values in the serializable object ”in this case, the Issue object. Next, invoke the formatter's Write method to perform the process of serializing the Issue object to the Body property of the Message object. Finally, send the message to its destination queue using the Send method. Listing 4-14 shows how to send a message containing a serialized object.

Listing 4-14: Sending a Message Containing a Serialized Object
start example
 public void SendBusinessObject( string strTargetProcess, string strSubject,     BusinessObject objSource ) {     string strTargetQueueName;     MessageQueue queue = null;     try     {         strTargetQueueName = ".\private$\" + strTargetProcess + "Inbox";         queue = new MessageQueue( strTargetQueueName );         Message message = new Message();         //specify the message formatter         BinaryMessageFormatter formatter = new BinaryMessageFormatter();         message.Formatter = formatter;         //set the message properties         message.Label = strSubject;         formatter.Write( message, objSource );         //send the binary serialized message         queue.Send( message );     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return; } 
end example
 

Deserializing an object from a message is just as easy. Again, establish a connection to a message queue. Next, create a Message object and assign it to the data returned from the MessageQueue object's Receive method. Instantiate a new BinaryMessageFormatter object and assign it to the Message object's Formatter property. Finally, create an instance of the serializable Issue object and assign it to the Body property of the retrieved message. The local Issue object will contain all data deserialized from the message body. Listing 4-15 shows how to receive a message containing a serialized object.

Listing 4-15: Receiving a Message Containing a Serialized Object
start example
 public BusinessObject ReceiveBusinessObject() {     MessageQueue queue = null;     BusinessObject objReceived = null;     try     {         queue = new MessageQueue( _QueueInboxName );         //retrieve message from the queue         Message message = queue.Receive();         //specify the message formatter         message.Formatter = new BinaryMessageFormatter();         //display retrieved object data         objReceived = (BusinessObject)message.Body;     }     catch( MessageQueueException exception )     {         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return objReceived; } 
end example
 

The difference between an XML-formatted message and a binary-formatted message is visually apparent. You can view the difference in the Computer Management console by selecting any of the application message queues, selecting a message, and viewing its properties, as shown in Figure 4-4.


Figure 4-4: Message properties displaying a binary-serialized message body

The decision between using XML formatting and using binary formatting is an important one to make early in the design process. XML formatting is more open and flexible to integrate with other client components. Binary formatting is faster and easier to implement when other client components are internally developed from the same code base and have access to the same business objects.

Working with Transactions

As mentioned, message queues can process messages in a transactional behavior. Transactions ensure that a series of messages are delivered in order, are delivered only once, and are successfully retrieved. When a transaction is processed , all messages are either committed or aborted. In the event of a failure in delivery, a transactional message queue can be rolled back to its original state.

To support transactional messages, create a message queue as transactional. As with regular message queues, you can create transactional message queues manually or programmatically. Figure 4-5 illustrates the creation of a transactional message queue using the Computer Management console.

click to expand
Figure 4-5: Creating a transactional message queue

Behavioral rules regarding messages and message queues are fairly clear. Nontransactional message queues are unable to process messages that are part of a transaction. However, if a nontransactional message is sent to a transactional message queue, the message is converted into a single-message transaction. Listing 4-16 shows how to send a series of messages as a transaction.

Listing 4-16: Sending a Series of Messages as a Transaction
start example
 public void SendTransactionMessages( string strTargetProcess,     ArrayList arrayMessages ) {     string strTargetQueueName;     MessageQueueTransaction queueTransaction = new MessageQueueTransaction();     MessageQueue queue = null;     try     {         strTargetQueueName = ".\private$\" + strTargetProcess + "Inbox";        queue = new MessageQueue( strTargetQueueName );        queueTransaction.Begin();         foreach( Message message in arrayMessages )         {             queue.Send( message, queueTransaction );         }         queueTransaction.Commit();     }     catch( Exception exception )     {         queueTransaction.Abort();         EventLog systemLog = new EventLog();         systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return; } 
end example
 

To execute a message-based transaction, instantiate a MessageQueueTransaction object. Next, instantiate a MessageQueue object and point it to the destination transactional message queue. Invoke the Begin method belonging to the MessageQueueTransaction object to mark the starting point of the messaging transaction. This sends each message, passing the MessageQueueTransaction object as a parameter to the Send method. Finally, invoke the Commit method to mark the end of the transaction. In the exception-handling block, the Abort method rolls back the entire transaction, including all messages sent or received.

Listing 4-17 creates a receive method to retrieve messages from a transactional message queue. If any exceptions are thrown during the message retrieval process, all read messages removed from the transactional message queue are restored as if they were never read. The message queue then returns to its original state marked by the Begin method.

Listing 4-17: Receiving a Message Within a Transaction
start example
 public Message ReceiveTransactionMessage() {     MessageQueueTransaction queueTransaction = new MessageQueueTransaction();     Message message = null;     MessageQueue queue = null;     try     {         queue = new MessageQueue( _QueueInboxName );         queueTransaction.Begin();         message = queue.Receive( queueTransaction );         queueTransaction.Commit();     }     catch( Exception exception )     {        queueTransaction.Abort();        EventLog systemLog = new EventLog();        systemLog.Source = "IssueTracker";         systemLog.WriteEntry( exception.Message, EventLogEntryType.Error, 0 );     }     finally     {         queue.Close();     }     return message; } 
end example
 

Receiving messages from a transactional message queue is similar to receiving normal messages. Begin by creating a MessageQueueTransaction object. Invoke its Begin method to mark the beginning of the retrieval transaction. Next, retrieve individual messages by invoking the MessageQueue object's Receive method, passing MessageQueueTransaction as a parameter. Once all messages within the transaction are received and processed, invoke the Abort method to mark the end of the retrieval transaction and close the message queue connection.




Developing. NET Enterprise Applications
Developing .NET Enterprise Applications
ISBN: 1590590465
EAN: 2147483647
Year: 2005
Pages: 119

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net