How to implement a receipt URL for reliable messaging that requires a user name and a password in BizTalk Server 2004 (904846)



The information in this article applies to:

  • Microsoft BizTalk Server 2004 Enterprise Edition
  • Microsoft BizTalk Server 2004 Standard Edition
  • Microsoft BizTalk Server 2004 Partner Edition
  • Microsoft BizTalk Server 2004 Developer Edition

INTRODUCTION

This article describes how to implement a receipt URL for reliable messaging that requires a user name and a password in Microsoft BizTalk Server 2004.

MORE INFORMATION

In Microsoft BizTalk Server 2002, a BizTalk Framework message can contain a receipt URL for reliable messaging that uses one of the following formats:
  • http://UserName:Password@Server/Resource.ext
  • https://UserName:Password@Server/Resource.ext
BizTalk Server 2002 can use this information to deliver the receipt to a URL that requires user name and password authentication.

BizTalk Server 2004 cannot send receipts to a URL that requires a user name and a password unless you create a custom pipeline component. This custom pipeline component extracts the user name and the password from the URL. To create this custom pipeline component, use code that is similar to the following Microsoft Visual C# code example.
using System;
using  System.Runtime.InteropServices;

namespace Microsoft.Samples.BizTalk.Pipelines.CustomComponent
{
	/// <summary>
	/// This helper class parses the HTTP URL that contains the user name and the password.
	/// </summary>
	class UrlHelper
	{
		[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
		private struct URL_COMPONENTS
		{
			public uint		dwStructSize;
			public string	szScheme;
			public uint		dwSchemeLength;
			public int		nScheme;  
			public string	szHostName;  
			public uint		dwHostNameLength;  
			public uint		nPort;  
			public string	szUserName;  
			public uint		dwUserNameLength;  
			public string	szPassword;  
			public uint		dwPasswordLength;  
			public string	szUrlPath;  
			public uint		dwUrlPathLength;  
			public string	szExtraInfo;  
			public uint		dwExtraInfoLength;
		}

		const int INTERNET_SCHEME_HTTP = 3;
		const int INTERNET_SCHEME_HTTPS = 4;

		const int INTERNET_MAX_HOST_NAME_LENGTH = 256;
		const int INTERNET_MAX_USER_NAME_LENGTH = 128;
		const int INTERNET_MAX_PASSWORD_LENGTH = 128;
		const int INTERNET_MAX_PATH_LENGTH = 2048;
		const int INTERNET_MAX_SCHEME_LENGTH = 32;      // This value is the longest protocol name length.
		const int INTERNET_MAX_URL_LENGTH = INTERNET_MAX_SCHEME_LENGTH + 3 + INTERNET_MAX_PATH_LENGTH;

		[DllImport("Wininet.dll", SetLastError=true, CharSet=CharSet.Unicode)]
		static private extern bool InternetCrackUrl (string url, int urlLength, int flags, ref URL_COMPONENTS urlComponents);

		/// <summary>
		/// This function tries to parse the user name and the password from the HTTP or HTTPS URL string.
		/// </summary>
		/// <param name="originalUrl">[in] Original URL</param>
		/// <param name="resultUrl">[out] If the function returns true, this parameter contains the URL that has the user name and the password removed.</param>
		/// <param name="userName">[out] param. If the function returns true, this parameter contains the user name from the URL.</param>
		/// <param name="password">[out] param. If the function returns true, this parameter contains the password from the URL.</param>
		/// <returns>
		///		true if the user name and the password were successfully parsed from the input URL.
		///		false if the URL is not an HTTP URL or does not contain the user name and password.
		/// </returns>
		static public bool ParseUserPasswordFromHttpUrl(string originalUrl, 
			out string resultUrl, 
			out string userName, 
			out string password)
		{
			bool	ContainAuthInfo = false;
			URL_COMPONENTS Result = new URL_COMPONENTS();
			
			// Initialize the parameters.
			resultUrl = string.Empty;
			userName = string.Empty;
			password = string.Empty;

			// Preallocate the buffer.
			Result.dwStructSize = (uint) Marshal.SizeOf(typeof(URL_COMPONENTS));
			Result.szScheme = new string(' ', INTERNET_MAX_SCHEME_LENGTH);
			Result.dwSchemeLength = INTERNET_MAX_SCHEME_LENGTH;
			Result.szHostName = new string(' ', INTERNET_MAX_HOST_NAME_LENGTH);  
			Result.dwHostNameLength=INTERNET_MAX_HOST_NAME_LENGTH;  
			Result.szUserName= new string(' ', INTERNET_MAX_USER_NAME_LENGTH);  
			Result.dwUserNameLength = INTERNET_MAX_USER_NAME_LENGTH;  
			Result.szPassword= new string(' ', INTERNET_MAX_PASSWORD_LENGTH);  
			Result.dwPasswordLength = INTERNET_MAX_PASSWORD_LENGTH;  
			Result.szUrlPath= new string(' ', INTERNET_MAX_PATH_LENGTH);  
			Result.dwUrlPathLength =INTERNET_MAX_PATH_LENGTH;  
			Result.szExtraInfo= new string(' ', INTERNET_MAX_URL_LENGTH);  
			Result.dwExtraInfoLength = INTERNET_MAX_URL_LENGTH;

			// Call the InternetCrackUrl function to parse the URL.
			// If the InternetCrackURL function does not parse the URL, the function returns False.
			if (InternetCrackUrl(originalUrl, originalUrl.Length, 0, ref Result))
			{
				// Only process the URL if the URL is HTTP or HTTPS.
				if (Result.nScheme == INTERNET_SCHEME_HTTP || 
					Result.nScheme == INTERNET_SCHEME_HTTPS)
				{
					// Run this code if the URL includes a user name and a password.
					if (Result.szUserName.Length > 0 || Result.szPassword.Length > 0)
					{
						if (Result.nPort == 80)
						{
							resultUrl = String.Format("{0}://{1}{2}{3}", 
								Result.szScheme, 
								Result.szHostName, 
								Result.szUrlPath, 
								Result.szExtraInfo);
						}
						else
						{
							resultUrl = String.Format("{0}://{1}:{2}{3}{4}", 
								Result.szScheme, 
								Result.szHostName, 
								Result.nPort, 
								Result.szUrlPath, 
								Result.szExtraInfo);
						}
						userName = Result.szUserName;
						password = Result.szPassword;
						ContainAuthInfo = true;
					}
				}
			}

			return ContainAuthInfo;
		}
	}
}
After you extract the user name and the password from the URL, set the authentication method, the user name, and the password in the message context. To do this, use code that is similar to the following Visual C# code example.
namespace Microsoft.Samples.BizTalk.Pipelines.CustomComponent
{
	using System;
	using System.Resources;
	using System.Drawing;
	using System.Collections;
	using System.Reflection;
	using System.ComponentModel;
	using System.Text;
	using System.IO;
	using Microsoft.BizTalk.Message.Interop;
	using Microsoft.BizTalk.Component.Interop;
	using Microsoft.XLANGs.BaseTypes;

	/// <summary>
	/// This code uses a custom pipeline component to extract the user name and the password from
	/// the HTTP URL in deprecated format. Then, the code sets the user name and the password in the message context.
	/// </summary>
	[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
	[ComponentCategory(CategoryTypes.CATID_Any)]
	[ComponentCategory(CategoryTypes.CATID_Validate)]
	[System.Runtime.InteropServices.Guid("041F4BBB-E37F-437f-9352-D761096CCF7A")]
	public class FixBTFHttp :
		IBaseComponent, 
		Microsoft.BizTalk.Component.Interop.IComponent,
		Microsoft.BizTalk.Component.Interop.IPersistPropertyBag
	{
		private static PropertyBase PropOutboundUrl = new BTS.OutboundTransportLocation();
		private static PropertyBase PropMessageType = new BTS.MessageType();

		private static PropertyBase PropHttpAuthType = new HTTP.AuthenticationScheme();
		private static PropertyBase PropHttpUsername = new HTTP.Username();
		private static PropertyBase PropHttpPassword = new HTTP.Password();

		/// <summary>
		/// </summary>
		public FixBTFHttp()
		{
		}

		#region IBaseComponent
        
		/// <summary>
		/// This code names the component.
		/// </summary>
		[Browsable(false)]
		public string Name
		{
			get {   return "FixBTFHttp Component";  }
		}
        
		/// <summary>
		/// This code sets the version of the component.
		/// </summary>
		[Browsable(false)]
		public string Version
		{
			get {   return "1.0";   }
		}
        
		/// <summary>
		/// This code sets the description of the component.
		/// </summary>
		[Browsable(false)]
		public string Description
		{
			get {   return "FixBTFHttp Pipeline Component"; }
		}
    
		#endregion
        
		#region IComponent

		/// <summary>
		/// This code implements the IComponent.Execute method.
		/// </summary>
		/// <param name="pc">Pipeline context</param>
		/// <param name="inmsg">Input message</param>
		/// <returns>Processed input message together with appended or prepended data.</returns>
		/// <remarks>
		/// The IComponent.Execute method is used to initiate
		/// the processing of the message in the pipeline component.
		/// </remarks>
		public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
		{
			IBaseMessageContext MsgContext		= null;
			object				PropertyValue   = null;

			string				OriginalUrl		= string.Empty;
			string				NewUrl			= string.Empty;
			string				Username		= string.Empty;
			string				Password		= string.Empty;
						
			MsgContext = inmsg.Context;

			// Verify that the message is a BizTalk Framework acknowledgement message.
			PropertyValue = MsgContext.Read(PropMessageType.Name.Name, PropMessageType.Name.Namespace);
			if (PropertyValue != null && (PropertyValue is string) && ((string)PropertyValue == "BTF2DeliveryReceipt"))
			{
				// Extract the outbound URL.
				PropertyValue = MsgContext.Read(PropOutboundUrl.Name.Name, PropOutboundUrl.Name.Namespace);
				if (PropertyValue != null && PropertyValue is string)
				{
					OriginalUrl = ((string) PropertyValue).Trim();
					// Verify that the URL is HTTP or HTTPS.
					if (OriginalUrl.ToLower(System.Globalization.CultureInfo.InvariantCulture).StartsWith("http"))
					{
						// Verify that the URL contains the user name and the password.
						if (UrlHelper.ParseUserPasswordFromHttpUrl (OriginalUrl, out NewUrl, out Username, out Password))
						{
							// Update the new outbound URL.
							MsgContext.Promote(PropOutboundUrl.Name.Name, PropOutboundUrl.Name.Namespace, NewUrl);
							// Set up HTTP basic authentication with the user name and the password.
							MsgContext.Write(PropHttpAuthType.Name.Name, PropHttpAuthType.Name.Namespace, "Basic");
							MsgContext.Write(PropHttpUsername.Name.Name, PropHttpUsername.Name.Namespace, Username);
							MsgContext.Write(PropHttpPassword.Name.Name, PropHttpPassword.Name.Namespace, Password);
						}
					}
				}
			}

			return inmsg;
		}
		#endregion

		#region IPersistPropertyBag
    
		/// <summary>
		/// This code obtains the class ID of the component that will be used. The code obtains the class ID from unmanaged code. 
		/// </summary>
		/// <param name="classid">Class ID of the component</param>
		public void GetClassID(out Guid classid)
		{
			classid = new System.Guid("041F4BBB-E37F-437f-9352-D761096CCF7A");
		}
        
		/// <summary>
		/// The InitNew method is not implemented. 
		/// </summary>
		public void InitNew()
		{
		}
        
		/// <summary>
		/// This code loads the configuration property for the component.
		/// </summary>
		/// <param name="pb">Configuration property bag</param>
		/// <param name="errlog">Error status. (This parameter is not used in this code.)</param>
		public void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Int32 errlog)
		{
		}
        
		/// <summary>
		/// This code saves the current component configuration in the property bag.
		/// </summary>
		/// <param name="pb">Configuration property bag</param>
		/// <param name="fClearDirty">Not used</param>
		/// <param name="fSaveAllProperties">Not used</param>
		public void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Boolean fClearDirty, Boolean fSaveAllProperties)
		{
		}

		#endregion
	}
}

Modification Type:MajorLast Reviewed:12/22/2005
Keywords:kbBTSMessaging kbhowto kbinfo KB904846 kbAudDeveloper