Introduction
The goal of this article is to explain the process of creating an ASP.NET project that allows privileged users to dynamically delete messages from Microsoft Message Queue (MSMQ).
There are many reasons to build this tool. Few of the primary reasons are as below:
- Allow to delete a single message rather than all messages from queue (purging the queue).
- Provide nice web front end for privileged users to delete messages from the queue.
- Keep track of deleted messages using a database.
Background
This article assumes readers' familiarity with MSMQ. If you are not familiar with MSMQ, you may want to research on it before you proceed. You may want to concentrate on topics like what is MSMQ, how to install and create queues, types of queues, accessing the queues and setting permissions to programmatically access the queue. You specifically want to read for private queues.
For more details, I strongly recommend visiting this link. (This link is active as of now, but if it is ever broken you can Google MSMQ or read about it on MSDN.)
Using the Code
Solution name: DQC (stands for Delete Queue Contents).
This solution contains five files around two projects (DQC and QMsngr):
- Two web forms (DQCMain.aspx and MSGDetails.aspx).
- An XML configuration file (DQConfig.xml).
- MS Access DB file (DQC.mdb)
- A Queue Messenger executable which used to send test messages to user created MSMQ. (QMsngr.exe)
Basic Setup
- Create a private queue called TestQueue. Donot check the transactional box.
- After you compile the project using Visual Studio .NET, copy DQC.mdb and DQConfig.xml to the C:\DQC directory.
Contents of the DQConfig.xml
<!---->
<!---->
<!---->
<?xml version="1.0" encoding="utf-8" ?>
<root>
<items>
<item> Admin Request </item>
<item> QA Request </item>
<item> Mgmt Request </item>
<item> Environment Request </item>
<item> Upgrade/Maintenance </item>
<item> Other </item>
</items>
<access>
<password>codeproject</password>
</access>
</root>
DelRecords Table in DQC.mdb
MessageID
of type Text
SentTime
of type DateTime
RequestingIP
of type Text
TimeOfDelete
of type DateTime
Reason
of type Text
Using the Code QMsngr.cs
Using the QMsngr.exe, we send messages to our recently created queue. Let�s dig into the QMsngr project:
MessageQueue testQ = new MessageQueue(".\\Private$\\TestQueue");
Message msg = new Message();
for (int msgCount = 1 ; msgCount <= 5; msgCount++)
{
msg.Label = "Test Message " + msgCount.ToString();
msg.Body = "This is body of Message" + msgCount.ToString() +
" The quick brown fox jumps over the lazy dog";
testQ.Send(msg);
}
Console.WriteLine("Transmission Complete");
We connect to the queue called TestQueue and send five messages to the queue; each with label Test Message <Number> and body This is body of Message <Message number> The quick brown fox jumps over the lazy dog. After the messages are sent to the queue, we display Transmission Complete on the screen.
Using the Code
DQCMain.aspx
Creating the table - creating the first row is done in the Page_Load
event. The reason for this is that later on even if we don�t have any messages in the queue, we still have the header row; since the number of rows that follow the header rows is equal to the number of messages in the queue.
We calculate the number of rows required by iterating through the Message Queue and counting the number of messages.
Total Number of the rows in the table = 1 Header Row + Total Number of Messages.
As we are iterating, we call the TableGen
method which accepts Message Label, Date and Time of the Message, and the Message ID. The TableGen
method generates entire table. The table has three columns Message Label, Date and Time when the message was sent to the queue, Message ID). The Microsoft way of creating table is creating cells and adding them to a row. Then we add rows to table. I do the same here. I also create a hyperlink to the MsgDetails.aspx and pass the MessageID to the TextBox
named msgId
. The hyperlink is assigned to the Message Label.
c.Controls.Add(new LiteralControl("<a href = MsgDetails.aspx?txtMsgId="+
msgId +">" + msgLabel));
MsgDetails.aspx
The MsgDetails.aspx has information about the selected message. It has details like MessageID, Proprity, Sent Time, Body, and reason of deletion. Specific details about the message can be added as per the design.
In the Page_Load
event, we retrieve the MessageID and store it in the MsgId TextBox
.
txtMsgId.Text = Request["txtMsgId"];
We populate the drop down list which states the reason for deleting the message in the code that follows. The DQConfig.xml contains all the details.
DataSet dataDoc = new DataSet();
dataDoc.ReadXml("C:\\DQC\\DQConfig.xml");
We reset the message queue cursor and iterate through the queue to find a match. Once the match is found, the message is identified and the details are retrieved and displayed in the appropriate server controls.
queueEnum.Reset();
while(queueEnum.MoveNext())
{
if(queueEnum.Current.Id == txtMsgId.Text)
{
break;
}
}
Users need to enter a password before they can delete message(s) from the queue. When the user clicks on the button Remove or Remove All, the password is verified and the current message is deleted from the queue and all the necessary details are added to the database.
Request.UserHostAddress
gets the IP address from which the delete request was issued.
Points of Interest:
- A
MessageEnumerator
is a cursor, initialized to the head of a dynamic list. The list order is the same as the order of the messages in the queue, according to message priority. You can move the cursor to the first message in the queue by calling MoveNext
. After the enumerator has been initialized, you can use MoveNext
to step forward through the remaining messages. You can specify whether to wait for a message to become available by passing a timeout into the MoveNext
method.
- There are certain properties of the current message enumerator which requires you to set
ReadPropertyFilter
.
So if you get an error message as below:
System.InvalidOperationException: Property SentTime was not retrieved
when receiving the message. Ensure that the PropertyFilter is set correctly.
Make sure you have MessageReadPropertyFilter.SetAll();
right before you create the MessageEnumerator
.
- If you get the below mentioned exception, check the access permissions for the queue:
Exception Details: System.Messaging.MessageQueueException:
Access to Message Queuing system is denied.