Pipeline to fix the odd promotion of AS2To.
See StackOverflow – Why field is promoted as AS2ToInternal instead of AS2To
This C# pipeline code checks for that issues and fixes it by promoting the proper field. I never found out why it was happening and why it needed to be fixed.
<pre>
using System;
using System.Linq;
using System.Xml;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;
using System.Resources;
using System.Reflection;
using System.Drawing;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Component.Utilities;
using Microsoft.XLANGs.RuntimeTypes;
using Microsoft.XLANGs.BaseTypes;
namespace Microsoft.Samples.BizTalk.Pipelines.CustomComponent
{
/// <summary>
/// Pipeline component which can't be placed into any receive or send
/// pipeline stage and do a property promotion / distinguished fields
/// writing based on arbitrary XPath.
/// </summary>
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_Any)]
[System.Runtime.InteropServices.Guid("EC9794D0-AE8F-42B9-A7E7-02A876998158")]
public class SetAS2ToPromotedField :
BaseCustomTypeDescriptor,
IBaseComponent,
Microsoft.BizTalk.Component.Interop.IComponent,
IPersistPropertyBag,
IComponentUI
{
private string _Encoding = "ASCII";
public SetAS2ToPromotedField()
: base(resourceManager)
{
}
#region Design-time property(ies)
#endregion
#region IBaseComponent Members
/// <summary>
/// Gets a component description.
/// </summary>
[Browsable(false)]
public string Description
{
get
{
return "SetAS2To from AS2ToInternal";
}
}
/// <summary>
/// Gets a component name.
/// </summary>
[Browsable(false)]
public string Name
{
get
{
return "SetAS2To";
}
}
/// <summary>
/// Gets a component version.
/// </summary>
[Browsable(false)]
public string Version
{
get
{
return "1.0";
}
}
#endregion
#region IComponentUI Members
/// <summary>
/// Validate component.
/// </summary>
/// <param name="projectSystem">Project system</param>
/// <returns>Enumerator of error message collection</returns>
public IEnumerator Validate(object projectSystem)
{
// Don't do any validation
return null;
}
/// <summary>
/// Gets a component icon.
/// </summary>
[Browsable(false)]
public System.IntPtr Icon
{
get
{
//return ((Bitmap) resourceManager.GetObject("CompIcon")).GetHicon();
return new System.IntPtr();
}
}
#endregion
#region IComponent Members
/// <summary>
/// Executes a pipeline component.
/// </summary>
/// <param name="pContext">Pipeline context</param>
/// <param name="pInMsg">Input message</param>
/// <returns>Outgoing message</returns>
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
try
{
return ExecuteInternal(pContext, pInMsg);
}
catch(Exception e)
{
// Put component name as a source information in this exception,
// so the event log in message could reflect this.
e.Source = Name;
throw e;
}
}
#endregion
#region IPersistPropertyBag Members
/// <summary>
/// Initialize component.
/// </summary>
public void InitNew()
{
// Do nothing
}
/// <summary>
/// Get component class ID.
/// </summary>
/// <param name="classID"></param>
public void GetClassID(out Guid classID)
{
classID = new Guid("1E7EC223-338E-4D4C-B275-FE9AD90ECE6C");
}
/// <summary>
/// Load component properties from a property bag.
/// </summary>
/// <param name="propertyBag">Property bag</param>
/// <param name="errorLog">Error log level</param>
public void Load(IPropertyBag propertyBag, int errorLog)
{
if ( null == propertyBag )
throw new ArgumentNullException("propertyBag");
//string val = (string)ReadPropertyBag(pb, "XPath1");
//if (val != null) _XPath1 = val;
string val;
//val = (string)ReadPropertyBag(propertyBag, "FilenamePattern");
//if (val != null) _FilenamePattern = val;
}
/// <summary>
/// Save component properties to a property bag.
/// </summary>
/// <param name="propertyBag">Property bag</param>
/// <param name="clearDirty">Clear dirty flag</param>
/// <param name="saveAllProperties">Save all properties flag</param>
public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
if ( null == propertyBag )
throw new ArgumentNullException("propertyBag");
//propertyBag.Write("FilenamePattern", _FilenamePattern);
}
#endregion
#region Private Members
/// <summary>
/// Executes the logic for this component.
/// </summary>
/// <param name="pContext">Pipeline context</param>
/// <param name="pInMsg">Input message</param>
/// <returns>Outgoing message</returns>
private IBaseMessage ExecuteInternal(IPipelineContext pContext, IBaseMessage pInMsg)
{
// Check arguments
if (null == pContext)
throw new ArgumentNullException("pContext");
if (null == pInMsg)
throw new ArgumentNullException("pInMsg");
if (null == pInMsg.BodyPart)
throw new ArgumentNullException("pInMsg.BodyPart");
if (null == pInMsg.BodyPart.GetOriginalDataStream())
throw new ArgumentNullException("pInMsg.BodyPart.GetOriginalDataStream()");
//
// Create a seekable read-only stream over the input message body part stream.
//
// Create a virtual stream, using GetOriginalDataStream() method on the IBaseMessagePart because
// this call doesn't clone stream (instead of IBaseMessagePart.Data property).
SeekableReadOnlyStream stream = new SeekableReadOnlyStream(pInMsg.BodyPart.GetOriginalDataStream());
//
// Create a new outgoing message, copy all required stuff.
//
// Create a new output message
IBaseMessage outMessage = pContext.GetMessageFactory().CreateMessage();
// Copy message context by reference
outMessage.Context = pInMsg.Context;
// Create new message body part
IBaseMessagePart newBodyPart = pContext.GetMessageFactory().CreateMessagePart();
// Copy body part properties by references.
newBodyPart.PartProperties = pInMsg.BodyPart.PartProperties;
// Neal added so we can change data in the message itself with replace commands
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();
byte[] byteArray;
_Encoding = "ASCII";
if (_Encoding == "UNICODE")
{
byteArray = System.Text.Encoding.Unicode.GetBytes(text);
}
else
{
byteArray = System.Text.Encoding.ASCII.GetBytes(text);
}
MemoryStream memStream = new MemoryStream(byteArray);
string strAS2Namespace = "http://schemas.microsoft.com/BizTalk/2006/as2-properties";
object objAS2To = pInMsg.Context.Read("AS2To", strAS2Namespace);
string strAS2To = objAS2To != null ? objAS2To.ToString() : "";
object objAS2From = pInMsg.Context.Read("AS2From", strAS2Namespace);
string strAS2From = objAS2From != null ? objAS2From.ToString() : "";
// Absolutely no idea why were are seeing the AS2To field promoted as AS2ToInternal.
// https://stackoverflow.com/questions/61464238/biztalk-as2-why-field-is-promoted-as-as2tointernal-instead-of-as2to
object objAS2ToInternal = pInMsg.Context.Read("AS2ToInternal", strAS2Namespace);
string strAS2ToInternal = objAS2ToInternal != null ? objAS2ToInternal.ToString() : "";
object objPreservedFileName = pInMsg.Context.Read("PreservedFileName", strAS2Namespace);
string strPreservedFileName = objPreservedFileName != null ? objPreservedFileName.ToString() : "";
string strHttpNamespace = "http://schemas.microsoft.com/BizTalk/2003/http-properties";
object objHttpHeaders = pInMsg.Context.Read("InboundHttpHeaders", strHttpNamespace);
string strHttpHeaders = objHttpHeaders != null ? objHttpHeaders.ToString() : "";
// could not get debugger to work, so had to try Trace
string trcGUID = System.Guid.NewGuid().ToString();
string trcData = "Filename=" + strPreservedFileName;
string trcXML = "InboundHttpHeaders:" + strHttpHeaders;
// If No AS2To found, then substitute the next best possible value by promoting it
string strNewAS2To = null;
if (String.IsNullOrEmpty(strAS2To))
{
// NOTE: if these two don't work, we could also parse out values from the Httpheader
// (but not there, the value is AS2-To
if (!String.IsNullOrEmpty(strAS2ToInternal))
{
strNewAS2To = strAS2ToInternal;
}
else
{
throw new System.ApplicationException("Could not find any possible value for AS2To. " +
" InboundHttpHeaders=" + strHttpHeaders
);
}
// based on above logic, promote a new value to AS2To if needed
//pInMsg.Context.Write("AS2To", strAS2Namespace, strNewAS2To);
pInMsg.Context.Promote("AS2To", strAS2Namespace, strNewAS2To);
}
// Set virtual stream as a data stream for the new message body part
//newBodyPart.Data = stream;
newBodyPart.Data = memStream;
pContext.ResourceTracker.AddResource(memStream);
// Copy message parts
CopyMessageParts(pInMsg, outMessage, newBodyPart);
//
// Return outgoing message.
//
return outMessage;
}
public static long GetRandomNumberInRange(double minNumber, double maxNumber)
{
double dblRandomNumber = new Random().NextDouble() * (maxNumber - minNumber) + minNumber;
long longRandomNumber = System.Convert.ToInt64(Math.Round(dblRandomNumber, 0));
return longRandomNumber;
}
/// <summary>
/// Copy message parts from source to destination message.
/// </summary>
/// <param name="sourceMessage">Source message</param>
/// <param name="destinationMessage">Destination message</param>
/// <param name="newBodyPart">New message body part</param>
private void CopyMessageParts(IBaseMessage sourceMessage, IBaseMessage destinationMessage, IBaseMessagePart newBodyPart)
{
string bodyPartName = sourceMessage.BodyPartName;
for (int c = 0; c < sourceMessage.PartCount; ++c)
{
string partName = null;
IBaseMessagePart messagePart = sourceMessage.GetPartByIndex(c, out partName);
if (partName != bodyPartName)
{
destinationMessage.AddPart(partName, messagePart, false);
}
else
{
destinationMessage.AddPart(bodyPartName, newBodyPart, true);
}
}
}
/// <summary>
/// Gets a message type from the XML stream.
/// </summary>
/// <param name="stream">Seekable stream</param>
/// <returns>Message type</returns>
/// <exception cref="InvalidOperationException">If message type can't be determined</exception>
private string GetMessageType(Stream stream)
{
// Fail if message stream is not seekable
if (!stream.CanSeek)
throw new InvalidOperationException("An attempt to get a message type for non-seekable stream");
// Save the current stream position
long position = stream.Position;
try
{
// Create XmlTextReader over the message stream
XmlTextReader reader = new XmlTextReader(stream);
while (reader.Read())
{
// Check if current node in XmlTextReader is element and it's global
if (XmlNodeType.Element == reader.NodeType && 0 == reader.Depth)
{
// Found a global element, now build message type as <namespaceURI>#<localName>
// if namespaceURI is not empty and <localName> if namespaceURI is empty.
if (reader.NamespaceURI != null && reader.NamespaceURI.Length > 0)
{
return reader.NamespaceURI + '#' + reader.LocalName;
}
else
{
return reader.LocalName;
}
}
}
// Not found, fail
throw new InvalidOperationException("Message type can't be determined");
}
finally
{
// Restore the stream position
stream.Position = position;
}
}
/// <summary>
/// Read property bag in order to eat the argument exception which is thrown
/// when properties are not populated.
/// </summary>
/// <param name="propertyBag">Property bag</param>
/// <param name="propertyName">Property name</param>
/// <returns>Property value</returns>
private object ReadPropertyBag(IPropertyBag propertyBag, string propertyName)
{
object value;
try
{
propertyBag.Read(propertyName, out value, 0);
return value;
}
catch (ArgumentException)
{
// IPropertyBag.Read throws an ArgumentException if there are no properties in there.
// Just return null in this case.
return null;
}
}
private SchemaList documentSchemaList = new SchemaList();
private static PromotingMap promotingMap = new PromotingMap();
private static ResourceManager resourceManager = new ResourceManager("Microsoft.Samples.BizTalk.Pipelines.CustomComponent.ArbitraryXPathPropertyHandler", Assembly.GetExecutingAssembly());
private static PropertyBase messageTypeWrapper = new BTS.MessageType();
//private const string DocumentSpecNamesPropertyName = "DocumentSpecNames";
//private const string DocumentSpecNamespacesPropertyName = "DocumentSpecNamespaces";
//private const char DocumentSpecNamesSeparator = '|';
#endregion
}
}
</pre>