A Custom MessageTypePromoter Pipeline Component

In my latest post, I’ve described a technique than allows to overcome a limitation of the ESB Toolkit 2.0 when performing context-based or content-based routing using the Business Rules Engine Resolver after an Orchestration in an Itinerary Service.

I described the use of a simple custom pipeline component used to determine and promote the type of the message before it reaches the ESB Dispatcher component responsible for executing Itineraries.

This is the source code of the custom MessageTypePromoter pipeline component.

A MessageTypePromoter Custom Pipeline Component

As you may be familiar by now, we’ll create a simple custom pipeline component thanks to the PipelineComponentBase class described earlier. As we’ve seen, this class performs all the necessary nitty-gritty work in order to streamline the production of world-class production-ready pipeline components.

[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_Decoder)]
[ComponentCategory(CategoryTypes.CATID_Encoder)]
[ComponentCategory(CategoryTypes.CATID_Any)]
[Guid("00000000-0000-0000-0000-000000000000")]
public class MessageTypePromoter : PipelineComponentBase, IComponent
{
}

Make sure to replace the empty Guid attribute with a brand new one, using UuiGen or a similar tool.

RecognizeMessageType

Firt, the RecognizeMessageType method that I described already in a previous post. The function is reproduced here for completeness and only slightly differs with an error handling catch block.

private string RecognizeMessageType(IPipelineContext pContext, IBaseMessage pInMsg)
{
    MarkableForwardOnlyEventingReadStream stream =
        new MarkableForwardOnlyEventingReadStream(
            pInMsg.BodyPart.GetOriginalDataStream());

    try
    {
        string messagetype = Utils.GetDocType(stream);

        WriteEventLog(Resources.MessageTypePromoter_RecognizingMessageType, messagetype);

        pInMsg.BodyPart.Data = stream;
        pContext.ResourceTracker.AddResource(stream);

        return messagetype;
    }

    catch (System.Exception /* e */)
    {
        WriteWarningLog(Resources.MessageTypePromoter_UnknownMessageType);
    }

    return String.Empty;
}

GetSchemaStrongName

Next, the GetSchemaStrongName method is added to the custom component, in order to determine the fully qualified name of the schema corresponding to the supplied MessageType.

The schema must be deployed in the BizTalk Management database and is found using Microsoft.BizTalk.ExplorerOM in order to iterate over all deployed schemas on the BizTalk group. In order for this to work, please add a reference to this assembly as found in the BizTalk Developer Tools folder.

private string GetSchemaStrongName(string messageType)
{
    string schema_tns = messageType.Substring(0, messageType.IndexOf('#'));
    string schema_root = messageType.Substring(messageType.IndexOf('#') + 1);

    try
    {
        BtsCatalogExplorer explorerOM = new BtsCatalogExplorer();
        explorerOM.ConnectionString = BtsConnectionString;

        foreach (Schema schema in explorerOM.Schemas)
            if (schema.RootName == schema_root && schema.TargetNameSpace == schema_tns)
                return schema.AssemblyQualifiedName;
    }

    catch (System.Exception e)
    {
         WriteWarningLog(Resources.MessageTypePromoter_UnknownSchemaStrongName);
    }

    return String.Empty;
}

The Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer is the main entry point in this assembly and represents the top-level hierarchy for navigating through all BizTalk artifacts on a BizTalk group. It must be configured with an appropriately formatted SQL Server connection string that describes the BizTalk Management database.

From a custom pipeline component running on a BizTalk host, it is possible to retrieve such a connection string, thanks to values stored in the registry. Indeed, the name and associated server of the BizTalk Management database are stored in a well-known location in the registry.

The following helper-property help format the required connection string:

private const string REG_KEY_BTS_ADMINISTRATION = @"SOFTWARE\Microsoft\BizTalk Server\3.0\Administration";
private const string MgmtDBName = "MgmtDBName";
private const string MgmtDBServer = "MgmtDBServer";

private static string BtsConnectionString
{
    get
    {
        RegistryKey hKey = Registry.LocalMachine.OpenSubKey(REG_KEY_BTS_ADMINISTRATION, false);
        return String.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI;"
            , hKey.GetValue(MgmtDBServer)
            , hKey.GetValue(MgmtDBName));
    }
}

IComponent

With those two functions, implementing the Execute method is very easy. Here is what it looks like:

#region IComponent Members

IBaseMessage IComponent.Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
    if (!Enabled)
        return pInMsg;

    WriteEventLog(Resources.MessageTypePromoter_StartExecute
        , pContext.PipelineName
        , CategoryName(pContext.StageID)
        );

    if (pInMsg == null)
    {
        WriteErrorLog(Resources.MessageTypePromoter_InvalidInMsg);
        return pInMsg;
    }

    // recognize message type

    string messageType = RecognizeMessageType(pContext, pInMsg);
    if (!String.IsNullOrEmpty(messageType))
    {
        pInMsg.Context.Promote("MessageType"
            , "http://schemas.microsoft.com/BizTalk/2003/system-properties"
            , messageType);

        string schemaStrongName = GetSchemaStrongName(messageType);

        pInMsg.Context.Promote("SchemaStrongName",
            "http://schemas.microsoft.com/BizTalk/2003/system-properties"
            , schemaStrongName);
    }

    // return the stream for subsequent components further down in the pipeline

    return pInMsg;
}

#endregion

First, the RecognizeMessageType method attempts to inspect the contents of the incoming message and determine its MessageType property. Upon success, the property is effectively promoted in the context of the incoming message.

Note that, as we’ve seen already, it is the responsibility of the RecognizeMessageType to ensure that the message stream does not get garbage collected. The method wraps the incoming message stream with an instance of the MarkableForwardOnlyEventingReadStream and uses it as a substitute for the original body part message stream.

Then, the GetSchemaStrongName method looks up the schemas currently deployed in the BizTalk Management database and attempt to find one that matches the determined MessageType. This is done by comparing the target namespace and root tag of each deployed schemas against the same components from the MessageType string.

IPersistPropertyBag

For simplicity, our component does not have any design-time properties. Therefore the implementation of IPersistPropertyBag is straightforward and is shown here for completeness.

#region IPersistPropertyBag Overrides

public override void InitNew()
{
    base.InitNew();
}

public override void Load(IPropertyBag propertyBag, int errorLog)
{
    base.Load(propertyBag, errorLog);
}

public override void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
    base.Save(propertyBag, clearDirty, saveAllProperties);
}

#endregion
This entry was posted in Pipeline Components. Bookmark the permalink.