Send to printer

An Introduction to MSMQ with ASP 

There are times when the functionality you need on your page takes an awful lot ofprocessing. This processing may take so long that the ASP page expires on the client or,even worse, your user gives up and goes to a different site. One possible solution forthis problem is to use Microsoft Message Queue (MSMQ) to off-load the processing toanother system.

Microsoft Message Queue

MSMQ (code name "Falcon") is a service that runs in Windows NT, whichprovides asynchronous communication between applications. It comes with NT4 Option Packand plays a part in Microsoft¡¯s DNA Architecture.

There are seven different installs for MSMQ, principally Primary EnterpriseController (PEC), Independent Client  and Dependent Client. PECserver is the one install option that runs the MSMQ network for enterprises, and only oneinstallation is needed. The Independent client install is typically installed on a webserver. An independent client can store messages locally, so it can function even if thePEC is unavailable. A Dependent Client install does not have its own local queue andtherefore will not function unless the PEC is available.

 

The basic concept of MSMQ is very simple: it is email for applications. A message ispackaged into a container of some kind and then stored in a queue at a particulardestination until the recipient is ready to read it. These queues mean that MSMQ canguarantee message delivery, regardless of the state or reliability of a networkconnection.

Just like an email message, MSMQ messages have a sender and receiver, where thereceivers are those who have access to the queue (NT¡®s security). So a single messagein a single queue can have multiple receivers - known as the responder. The senderof the message is usually the web server (IIS) and the responder is a custom applicationthat resides on a different machine. Although the custom application doesn¡¯t have tobe on a different machine, the system allows greater scalability.

MSMQ can also communicate with other messaging systems. Level 8 Systems isdeveloping a third-party solution to communicate with MSMQ on platforms such as SunSolaris, HP-UNIX, OS/2, VMS, and AS/400 platforms. For more information check out theirweb site at http://www.level8.com.

MSMQ architecture comprises one to N servers that route, send, and receive messages.Each one of these servers may or may not contain multiple message queues, and within eachqueue may reside one or more messages. Independent client installations can also containmultiple messages within multiple queues.

Just like most BackOffice services, MSMQ has a COM API ( mqoa.dll ) towhich developers can program. Although MSMQ¡¯s object model is comprised of nineclasses, most applications only need to utilise three of these: MSMQQueueInfo,MSMQQueue, and MSMQMessage.

MSQMQueueInfo

MSMQQueueInfo allows you to create, open or delete message queues. Inorder to work with a queue the PathName must first be set, a property whichnames the queue and tells MSQM on which machine the queue can be located.

  <% 
   Dim objQueueInfo
   Dim objQueue
   Set objQueueInfo=Server.CreateObject("MSMQ.MSMQQueueInfo")
   objQueue.PathName = ".\MyQueue"
   Set objQueue = objQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
  %>

The code opens a queue named MyQueue located on the local computer. First,a variable is dimensioned (optional, but good practice), and then an instance of the MSMQQueueInfoclass created. The next line sets the name and location of the desired queue and the lastline opens the queue. If the queue is located on a different machine simply replace theperiod with the server name.

  objQueue.PathName = "SomeOtherComputer\MyQueue"

When opening a queue, two parameters must be specified: Access and ShareMode.Access specifies what is going to be done with the queue. The possible valuesfor this parameter are defined in the MQACCESS enumerator. The MQACCESSenumerator has three possible values: MQ_PEEK_ACCESS (32), MQ_RECEIVE_ACCESS(1), and MQ_SEND_ACCESS (2). MQ_PEEK_ACCESS is used to look(peek) at messages within the specified queue, but it does not remove them. When MQ_RECEIVE_ACCESSis specified, messages in the queue are removed as they are read. MQ_SEND_ACCESSis used to send messages to the queue, not to receive messages. Note that the openroutine returns a reference to a MSMQQueue object. This object is whatmessages are sent and received from. The MSMQQueue is further discussedbelow.

Creating and deleting message queues is performed much like opening a queue. Set the PathNameand then call the Create or Delete routine.

  <% 
   Dim objQueue
   Set objQueue = Server.CreateObject("MSMQ.MSMQQueueInfo")
   objQueue.PathName = ".\MyQueue"
   objQueue.Create
  %> 
  <%
   Dim objQueue
   Set objQueue = Server.CreateObject("MSMQ.MSMQQueueInfo")
   objQueue.PathName = ".\MyQueue"
   objQueue.Delete
  %>

MSMQQueue

The MSMQQueue class represents an opened queue that resides the inMSMQ service. This class provides the ability to loop through the messages within a queuein a cursor-like manner. You can't open a queue using the MSMQQueue class; todo that you ned to use the MSQMQueueInfo (see above).

Even though most ASP applications that utilise MSMQ usually send messages, there aretimes when ASP pages, administrative pages for example, need to open a queue and displaythe messages. Most MSMQ applications also need a server-side piece to respond to themessages as they are received and act accordingly.

Messages can be retrieved from a queue synchronously or asynchronously, although ASPpages can only retrieve messages synchronously. This is because ASP pages can not declarea sever-side variable WithEvents, which makes sense since the life of avariable in an ASP page is relatively short. The server application that responds to themessages and processes them, however, really benefits from asynchronous message retrieval.Accessing MSMQ messages asynchronously allows the application to wait for an event to firewhen a new message appears in the queue. This means the server side applicationdoesn¡¯t use up resources while it sits in a loop waiting to receive a message. The MSMQEventprovides events that the server side application can respond to.

The following code example shows how to open a local queue and respond to messagesasynchronously through the use of the MSMQQueue and MSMQEventclasses. (There is a MSMQMessage class that is used to hold information for amessage and it is returned from the PeekCurrent routine, which we'll look atfurther on.)

Option Explicit
Dim m_objQueueInfo As New MSMQQueueInfo
Dim m_objQueue As MSMQQueue
Dim WithEvents m_objMSMQEvent As MSMQEvent
Private Sub Form_Load()
  'Create the queue, ignoring the error if it's already there
  m_objQueueInfo.PathName = ".\MyQueue"
  m_objQueueInfo.Label = "My Sample Queue"
  
  'If queue already exists iqnore it
  On Error Resume Next
  m_objQueueInfo.Create
  On Error GoTo 0
  
  'open the queue
  Set m_objQueue = m_objQueueInfo.Open(MQ_RECEIVE_ACCESS, _    MQ_DENY_NONE)
    
  'link the events 
  Set m_objMSMQEvent = New MSMQEvent
  m_objQueue.EnableNotification m_objMSMQEvent, MQMSG_CURRENT, 1000
End Sub
Private Sub m_objMSMQEvent_Arrived(ByVal Queue As Object, _
ByVal Cursor As Long)
  'process the message
  Dim m_objMessage As MSMQMessage
  Set m_objMessage = Queue.PeekCurrent
  MsgBox "Message Received: " & m_objMessage.Label
  
  'link the event for the next message
  m_objQueue.EnableNotification m_objMSMQEvent, MQMSG_NEXT, 10000
End Sub
Private Sub m_objMSMQEvent_ArrivedError(ByVal Queue As Object, _
ByVal ErrorCode As Long, ByVal Cursor As Long)
  'something went wrong!
  MsgBox "Error accorded: " & ErrorCode
End Sub

This code starts out with the Form_Load event and creates a queue if onedoesn¡¯t exist already. Next, the m_objMSMQEvent object is set up and andlinked by calling the EnableNotification method on the MSMQQueueobject. Once the MSMQEvent object is linked, the only thing left to do isfulfill the Arrived and Arrived_Error (optional) events. The Arrivedevent will fire every time a new message is sent to the queue (as long as the EnableNotificationmethod was called following the occasion that a message was read). This event returns tworeferences, one to the queue that the message can be read from and one to the Cursor.If an error occurs, the ArrivedError event will fire with reference to thequeue object, a long variable holding the error number, and a reference to the cursor.

When retrieving messages synchronously the code execution is halted until a message isavailable or the time-out expires. The following code opens a queue and peeks at all ofthe messages it contains:

Public Sub DisplayMessages()
  Dim objQueueInfo As New MSMQQueueInfo
  Dim objQueue As MSMQQueue
  Dim objMessage As MSMQMessage
  
  'Create the queue, ignoring the error if it's already there
  objQueueInfo.PathName = ".\MyQueue"
  objQueueInfo.Label = "My Sample Queue"
  
  'If queue already exists iqnore it
  On Error Resume Next
  objQueueInfo.Create
  On Error GoTo 0
  
  'open the queue
  Set objQueue = objQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
     
  'Loop through all the messages in the queue
  Do While True
     Set objMessage = objQueue.Peek(, , 1000)
     If objMessage Is Nothing Then Exit Do
     MsgBox "Message: " & objMessage.Label
  Loop
  MsgBox "No more new messages."
  'clean up
  objQueue.Close
  Set objMessage = Nothing
  Set objQueue = Nothing
  Set objQueueInfo = Nothing
End Sub

The code above starts off the same as the asynchronous code, but instead of using the MSMQEventclass, it enters a Do While ¡­ Loop until the Peek routine of the MSMQQueueclass returns nothing. This routine returns nothing when the time-out expires, which inthis case is set to 1000 milliseconds.

MSMQMessage

The MSMQMessage class holds all of the attributes for a message in aqueue. MSMQ messages have two methods and a great deal of attributes. I'm going to focuson the two main attributes - Body and LabeL - and the one mainmethod - Send .

A MSMQMessage is designed to hold all sorts of information. In fact, the Bodyof the message can hold a string, byte array, and any numeric types. The body of a messageis also designed to hold the state of any COM object that supports the IPersist andIDispatch interfaces. The Label of a message is a string that simplydescribes it - very similar to the title of an email message.

There are two ways to retrieve a message: opening and peeking. A message is removedfrom the queue after it has been opened, but remains in the queue until expiry if it'speeked. The open and Peek routine are carried out in the MSMQQueueclass, and both return a reference to the message. The following code opens a message, anddisplays the Body and Label.

Private Sub LookForMessage()
  Dim objQInfo As New MSMQQueueInfo
  Dim objQReceive As MSMQQueue
  Dim objMessage As MSMQMessage
  'open the queue
  objQInfo.PathName = ".\test" 'or ".\test"
  Set objQReceive = objQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
  
  'look for a message (time-out set to 100 ms)
  Set objMessage = objQReceive.Receive(, , , 100)
  'was there a messsage?
  If Not objMessage Is Nothing Then
    'display the contents of the message
    MsgBox objMessage.Label & " - " & objMessage.Body
  Else
    'no message
    Msgbox "Nothing in the queue"
  End If
  
  'clean up
  objQReceive.Close
  Set objQInfo = Nothing
  Set objQReceive = Nothing
  Set objMessage = Nothing
End Sub

This code begins by opening a queue and looking for a message within it. The Receiveroutine, with a time-out of 100 milliseconds, is called to look for the messages. Thetime-out tells the MSMQ library to stop waiting for a message, and a very low time-outperiod means that the code is just checking for existing messages rather than waiting fora message to be received. If the user wants to see if there is a message they can simplycheck whether the Receive function has returned a reference to a validmessage or if it returned Nothing (If Not objMessage Is Nothing). If theroutine did return a message then the code displays the Label and the Bodyof the message.

Sending a message is the most common task performed with MSMQ in ASP. To send amessage, a Queue must be opened with send access (MQ_SEND_ACCESS ) and amessage must be prepared, before invoking the Send routine on the MSMQMessageclass.

<%
  
  'dim some variables  
  Dim objQInfo
  Dim objQSend
  Dim objMessage
     
  'open the queue
  Set objQInfo = Server.CreateObject("MSMQ.MSMQQueueInfo")
  objQInfo.PathName = ".\test" 
  Set objQSend = objQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
  
  'build/send the message
  Set objMessage = Server.CreateObject("MSMQ.MSMQMessage")
  objMessage.Label = "This is the label."
  objMessage.Body = "This is the body."
  objMessage.Send objQSend
  objQSend.Close
  'clean up
  Set objQInfo = Nothing
  Set objQSend = Nothing
  Set objMessage = Nothing
%>

This code opens a queue with send messages access, and the message is assembled bycreating an instance of a message class and setting a Label and a Body. The Message is sent by calling the Send routine on the queue and passing a referencethrough a parameter.

Uses for MSMQ

There are many possible uses for MSMQ in an application, but the most common is that ofoff-loading processing to another thread (MSMQ on the same machine as IIS) and/or toanother computer (MSMQ on a different machine as IIS). By moving processing to anotherthread and/or machine the ASP page can continue processing (executing server-side code).

Two things normally determine whether you will want to move task processing: processingtime itself, and whether the user expects feedback or not. Processing can be shiftedelsewhere so long as the user doesn¡¯t expect immediate feedback - recalculatinginventory levels after an order has been placed, for example. If a task on the server-sideprocessing of the web page takes too long, then there is a risk that the page willtime-out on the user and they'll receive an error message, hit refresh or simply give up.  Shifting the processing, otherwise known as background processing, not only speedsup the processing time of a page itself, but also benefits the web server scale.

MSMQ also has the ability to hold certain Component Object Model (COM) objects in thebody of the message. The only requirement is that the COM objects support IDispatchand IPersist ( IPersistStream or IPersistStorage )interfaces. This is really easy to use, and saves masses of coding time and effort. Twoclasses that support these interfaces are ADODB.Recordset (and ADOR.Recordset) and Word.Document. This final code example sends an ADODB.Recordsetas the body of a MSMQMessage .

Public Sub SendRecordsetInMessage()
    
  Dim objQInfo As New MSMQ.MSMQQueueInfo
  Dim objQSend As MSMQ.MSMQQueue
  Dim objMessage As New MSMQ.MSMQMessage
  Dim objRS As New ADOR.Recordset
  
  Dim a As New MSMQQueue
    
  'build/fill the recordset
  With objRS
    'build the recordset structure
    .CursorLocation = adUseClient
    .Fields.Append "FN", adVarChar, 25
    .Fields.Append "LN", adVarChar, 25
    .Open
    
    'add a record
    .AddNew
    .Fields("FN") = "Chris"
    .Fields("LN") = "Blexrud"
    .Update
    
    'add a record
    .AddNew
    .Fields("FN") = "Shayna"
    .Fields("LN") = "Blexrud"
    .Update
  End With
  
  'build/get the queue
  objQInfo.PathName = ".\test"
  Set objQSend = objQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
  
  'build/send the message
  objMessage.Label = "Recordset State!!!!"
  objMessage.Body = objRS
  objMessage.Send objQSend
  objQSend.Close
  ¡®clean up 
  Set objQInfo = Nothing
  Set objQSend = Nothing
  Set objMessage = Nothing
  Set objRS = Nothing
End Sub

When the recordset is retrieved from the message it is Set to the Bodyof the message, because the Body of the message is stored as a Variant

  Set objRS = objMessage.Body

Considerations

There is one difficulty with developing applications with MSMQ - the lack of feedback.Although it makes sense in the architecture of MSMQ, some tricky code is called for if youwant to find the results of a task that was assigned to another process through a MSQMmessage.

There are ways to get around this problem. You could send a message back to the user,so if the user wants to know if the task was carried out they can check the queue. Or youcan use a table in a database to track tasks assigned through MSMQ and define a set ofstandards to be fulfilled by each MSMQ user. Then users can lookup the tasks in a table tosee if they have been processed, and whether they succeeded or failed.

One final note - the install footprint of MSMQ is quite large. The server installrequires SQL Server (6.5 or 7) to be installed first, in order to keep track of thevarious queue information. Although the client install doesn¡¯t require a preinstalleddatabase, its footprint is still pretty large.