Click here to Skip to main content
15,896,557 members
Articles / Web Development / ASP.NET

Secure Web Services via Message oriented Middleware

Rate me:
Please Sign up or sign in to vote.
4.96/5 (43 votes)
1 May 200372 min read 337.3K   821   198  
Describes an approach for delivery of Soap Messages serialised using ASP.NET Web Client Services over MSMQ and MQ
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Messaging;
using System.Web.Services.Protocols;


namespace WSAltTransports
{
	/// <summary>WSAltTransports.MSMQRequestCreator</summary>
    /// <author>SGregory</author>
    /// <date>02 January 2003</date>
    /// <remarks>Creation Factory for MSMQ-style WebRequests</remarks>
    public sealed class MSMQRequestCreator : IWebRequestCreate
	{
		/// <summary>WSAltTransports.MSMQRequestCreator.Create</summary>
        /// <author>SGregory</author>
        /// <date>02 January 2003</date>
        /// <remarks>Creates an MSMQWebRequest object</remarks>
        /// <param name="Uri" type="System.Uri">URI indicating endpoint</param>
        /// <returns type="System.Net.WebRequest">MSMQWebRequest object</returns>
        public WebRequest Create(
			Uri Uri)
		{
			// Create a new request
			return new MSMQWebRequest(
				Uri,
				m_ResponseUri);
		}


		/// <summary>WSAltTransports.MSMQRequestCreator.ResponseUri</summary>
        /// <value>Response Url for the request</value>
        public Uri ResponseUri
		{
			get
			{
				return m_ResponseUri;
			}
			set
			{
				m_ResponseUri = value;
			}
		}


		// Private member data
		private Uri m_ResponseUri = null;
	}


	/// <summary>WSAltTransports.MSMQWebRequest</summary>
	/// <author>SGregory</author>
	/// <date>16 January 2003</date>
	/// <remarks>This class handles the specific implementation of the MSMQ transport</remarks>
    public sealed class MSMQWebRequest : MyBaseWebRequest
	{
		/// <summary>WSAltTransports.MSMQWebRequest</summary>
		/// <author>SGregory</author>
		/// <date>02 January 2003</date>
		/// <remarks>This is the ; point for all "MSMQ://" requests</remarks>
		public MSMQWebRequest(
			Uri Url,
			Uri ResponseUrl)
		{
			// Check the scheme for the Uri is as expected
			if(Url.Scheme.ToLower() != "msmq")  // This class is only for msmq urls
				throw new NotSupportedException("This protocol [" + Url.Scheme + "] is not supported");
			
			// Set data members
			m_RequestUri = Url;
			m_szMethod = string.Empty;  // No default for this class
			this.ResponseUri = ResponseUrl;
		}


		// Force MSMQ to derive a default response Queue name from the supplied request name
		// Simply add the _resp to the AbsoluteUri to create a new Uri
		protected override void SetResponseUriFromRequestUri(Uri requestUri)
		{
			this.ResponseUri = new Uri(requestUri.AbsoluteUri + "_resp");
		}


		/// <summary>WSAltTransports.MSMQWebRequest.GetResponse</summary>
		/// <author>SGregory</author>
		/// <date>16 January 2003</date>
		/// <remarks>
		/// Does the work of placing the serialised Soap message on the named Queue
		/// and waiting for a response on the Response Queue.
		/// NOTES:
		/// 1) The URI for the MSMQ Queue names look like:
		///			msmq://./private$/Q1
		///	   This is simply because the Uri processor does not seem to like the format:
		///	   		msmq:.\private$\Q1
		///	   Hence, the URI needs to be processed slightly to reverse slashes etc.
		///	   If anyone finds a nice way to get round this please let me know.		
		/// 2) The default Response Queue to wait on is "&lt;queuename&gt;_resp"
		///    e.g. if an outbound Queue is called ".\private$\Q1" the response Queue will
		///    be named ".\private$\Q1_Resp" by default unless it is changed by calling the
		///    public property <code>ResponseUri</code> on this class.
		/// </remarks>
		/// <returns type="System.Net.WebResponse">Our own special brand of WebResponse</returns>
		public override WebResponse GetResponse() 
		{
			MSMQWebResponse objMSMQWebResponse = null;
			System.Messaging.Message msg = null;
			MessageQueue objQueue = null;

			try
			{
				// Figure out the return URI, or generate one on the fly
				if (this.ResponseUri == null)
					SetResponseUriFromRequestUri(m_RequestUri);

				// We need to interpret the MSMQ URI a little
				string strQueueName = this.m_RequestUri.LocalPath.Replace("/", "\\");
				if (strQueueName.ToUpper().IndexOf("PRIVATE$") >= 0)
					strQueueName = "." + strQueueName;

				// We need to interpret the response MSMQ URI a little too!
				string strQueueNameResp = this.ResponseUri.LocalPath.Replace("/", "\\");
				if (strQueueNameResp.ToUpper().IndexOf("PRIVATE$") >= 0)
					strQueueNameResp = "." + strQueueNameResp;

				// Create an MSMQ message.  It requires:
				// 
				//   * a label
				//   * a body
				//
				// We shall make it recoverable
				System.Messaging.Message objMsg = new System.Messaging.Message();
				objMsg.BodyStream = m_RequestStream;
				objMsg.Recoverable = true;
				objMsg.ResponseQueue = new MessageQueue(strQueueNameResp);

				// Open the Queue for writing
				objQueue = GetQ(strQueueName, true);

				try
				{
					// Send the message under a single MSMQ internal transaction
					objQueue.Send(objMsg, MessageQueueTransactionType.Single);
				}
				catch(Exception e)
				{
					// An exception occured when sending - this can be down to the
					// response queue (!) that was supplied in the objMsg.ResponseQueue
					// slot not existing
					if (e.Message.IndexOf("Queue is not registered in the DS.") >= 0)
					{
						throw new WebException(
							"Failed to locate MSMQ response queue '" + strQueueNameResp + "' when trying to send request.",
							e,
							WebExceptionStatus.NameResolutionFailure,
							null);
					}
					else
					{
						// Throw the exception as a geenral send failure
						throw new WebException(
							"Failed to send the message to the MSMQ request queue.", 
							e,
							WebExceptionStatus.SendFailure,
							null);
					}
				}
				finally
				{
					// Free resources on main Queue
					objQueue.Close();
					
					// And close serialised Soap Stream....
					m_RequestStream.InternalClose();
				}

				// Get the ID of the outbound message to use as the Correlation ID
				// to look for on any inbound messages
				string strCorrelationID = objMsg.Id;

				// Either open or create a private MSMQ Queue on the local machine
				try
				{
					TimeSpan tWaitResp = new TimeSpan(0, 0, m_intTimeout / 1000);
					objQueue = GetQ(strQueueNameResp, false);

					// The receive must be done in a loop since, by observation it has
					// been seen that other threads may manipulate the MSMQ read cursor
					// underneath us (as we can't sharedenyall).
					// So we need to effectively retry the receive, knowing that no-one else
					// will be after OUR message as no-one else should have our Correlation ID.
					bool blnRetryReceive = true;
					while (blnRetryReceive == true)
					{
						try
						{
							// Wait for a message with our moniker one it
							msg = objQueue.ReceiveByCorrelationId(strCorrelationID, tWaitResp, MessageQueueTransactionType.Single);
							// If we've found it we are done
							blnRetryReceive = false;
						}
						catch(Exception e)
						{
							// If this was an error other than the cursor error we
							// are expecting then throw it to our next handler
							if (e.Message.IndexOf("Message that the cursor is currently pointing to has been removed from the queue") < 0)
								throw e;
						}
					}
				}
				catch(Exception e)
				{
					// This handler handles exceptions from the receive process that are non-retryable
					// These include timeouts waiting on the message, and all other exceptions.
					if (e.Message.IndexOf("Timeout for the requested operation has expired") >= 0)
					{
						// Throw the exception as a Timeout
						throw new WebException(
							"Timeout waiting on MSMQ resource.", 
							e,
							WebExceptionStatus.Timeout,
							null);
					}
					else
					if ((e is System.Net.WebException) == false)
					{
						// Just throw it up the way as a Receive failure
						throw new WebException(
							"Failed to receive the message from the MSMQ response queue.", 
							e,
							WebExceptionStatus.ReceiveFailure,
							null);
					}
				}
				finally
				{
					// Free resources on main Queue
					objQueue.Close();
				}
				
				// If we get here all is well.
				// Create a return, and stream results into it...
				objMSMQWebResponse = new MSMQWebResponse();
				objMSMQWebResponse.SetDownloadStream(msg.BodyStream);
			}
			catch(Exception e)
			{
				// All exceptions caught here that are not of the WebException variety
				// get cast as ProtocolErrors - these are exceptions that we do not have
				// a mapping to a standard WebException status type.

				// Close serialised Soap Stream....if it needs closing
				m_RequestStream.InternalClose();

				// Have we already processed this exception in some way?
				if ((e is System.Net.WebException) == false)
				{
					// No - Create a MSMQWebResponse - this is a protocol specific error
					objMSMQWebResponse = new MSMQWebResponse(
						(int)QueueTransportErrors.UnexpectedFailure, 
						e.Message, 
						e.StackTrace);

					// And throw the exception
					throw new WebException(
						"MSMQ Pluggable Protocol failure.", 
						e,
						WebExceptionStatus.ProtocolError,
						objMSMQWebResponse);
				}
				else
				{
					// It's prepared already from a lower level, so just
					// throw it on up the chain
					throw e;
				}
			}

			// If we get here we are in business, so return a repsonse
			return objMSMQWebResponse;
		}	

	
		/// <summary>WSAltTransports.MSMQWebRequest.GetQ</summary>
        /// <author>SGregory</author>
        /// <date>16 January 2003</date>
        /// <remarks>Attempts to open a Queue with the passed name</remarks>
        /// <param name="vstrQueueName" type="string">Name of Queue to open</param>
        /// <param name="vblnIsRequestQueue" type="System.bool">true if this is a request queue</param>
        /// <returns type="System.Messaging.MessageQueue">Type instance representing a Queue</returns>
        private MessageQueue GetQ(
			string vstrQueueName,
			bool vblnIsRequestQueue)
		{
			MessageQueue msgQ = null;
			
			// Does the Queue exist?
			if (MessageQueue.Exists(vstrQueueName) == false)
			{
				string strTypeOfQueue = (vblnIsRequestQueue == true) ? "request" : "response";
				string strTypeOfOperation = (vblnIsRequestQueue == true) ? "send request" : "read response";

				// No - report this
				throw new WebException(
					"Failed to locate MSMQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
					WebExceptionStatus.NameResolutionFailure);
			}
			else
			{
				try
				{
					// The Queue exists already.  So just open it
					msgQ = new MessageQueue(vstrQueueName);
				}
				catch(Exception e)
				{
					string strTypeOfQueue = (vblnIsRequestQueue == true) ? "request" : "response";
					string strTypeOfOperation = (vblnIsRequestQueue == true) ? "send request" : "read response";

					throw new WebException(
						"Failed to open MSMQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
						e,
						WebExceptionStatus.ConnectFailure,
						null);
				}
			}

			// Either way, return the Queue object to the caller
			return msgQ;
		}
	}


	/// <summary>WSAltTransports.MSMQWebResponse</summary>
	/// <author>SGregory</author>
	/// <date>16 January 2003</date>
	/// <remarks>
	/// The MSMQ Response - we totally derive from MyBaseWebResponse here
	/// </remarks>
	public sealed class MSMQWebResponse : MyBaseWebResponse
	{
		/// <summary>WSAltTransports.MSMQWebResponse.MSMQWebResponse</summary>
        /// <author>Simon G</author>
        /// <date>24 February 2003</date>
        /// <remarks>Default constructor - used simply to set up the base class member data</remarks>
        internal MSMQWebResponse()
		{
		}


		/// <summary>WSAltTransports.MSMQWebResponse.MSMQWebResponse</summary>
        /// <author>Simon G</author>
        /// <date>24 February 2003</date>
        /// <remarks>Error information constructor</remarks>
		/// <param name="vintStatusCode" type="int">WebResponse Code e.g. 500</param>
		/// <param name="vstrStatusDescription" type="string">WebResponse Error description</param>
		/// <param name="vstrLog" type="string">Associated stack trace or similar</param>
        /// <postconditions>Error information set up</postconditions>
        internal MSMQWebResponse(
			int vintStatusCode, 
			string vstrStatusDescription, 
			string vstrLog) 
		{
			// Delegate to the base class for the setting up of the 
			// error information.  This is explicitly shown here through
			// the use of "base."
			base.SetErrorInformation(
				vintStatusCode, 
				vstrStatusDescription, 
				vstrLog);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
I am the Technical Director for Myphones.com, which specialises in developing integrated VoIP products and services for the consumer and SME markets. Technology-wise, we are heavily into the 2nd generation WS stack atop .NET, and basically most things Microsoft, as well as C++ on Linux.

Comments and Discussions