Serializing Message Context and Part Properties

In a recent project, our customer already had an in-house monitoring platform, in order to monitor the activities or our solution, in terms of received messages and transmitted messages a well as in terms of errors.

However, this platform did not support the possibility to re-submit a message in case of an error.

That’s why I set up a lightweight resubmit solution. My solution consists in a very simple database used to hold messages that need to be submitted back to the solution at a later time. The purpose of this solution is to keep the message box clean (no suspended messages) and allow the messages to be retrieved and re-submitted, subject to business criteria.

This post is the first post in a series where I will talk a bit more about this solution. Well, it’s not so much the solution that we’re interested in here, but the technical challenges I had to overcome in order to successuflly deliver a working solution.

Today, I would like to talk about how to serialize the context of a BizTalk message so that we can store it inside the database. I will also talk about the similar action that consists in serializing the properties associated with each message part.

Serializing the Context of BizTalk Messages

The simplest way to serialize the context of a BizTalk message consists in letting the BizTalk runtime do it. In order to achieve this, we need to somehow convince BizTalk that our message context is serializable.

In terms of classes and interfaces, this means that we need to cast an IBaseMessageContext interface, that represents our message context in BizTalk land, to an interface that supports serialization. The most appropriate candidate is the IPersistStream interface.

In BizTalk, the most likely place we can deal with an IBaseMessageContext interface is in pipeline components. Unfortunately, the instance associated with this interface as retrieved from the incoming message cannot be cast to an IPersistStream interface.

The IPersistStream interface leaves in the Microsoft.BizTalk.Component.Interop namespace of the Microsoft.BizTalk.Pipeline assembly.

In order to achieve our goal, we need to use a message context that comes from the BizTalk Messaging Agent Factory. This is, as far as I know, a little undocumented feature, that I discovered thanks to Randal’s excellent post on his blog.

In order to make it simpler to retrieve the Messaging Agent Factory, I created the following helper Property-Get:

internal static IBTMessageAgentFactory MessageFactory
{
    get {
        return (((IBTMessageAgent)new BTMessageAgent()) as IBTMessageAgentFactory);
    }
}

...
// create a message context that can be cast to IPersistStream
IBaseMessageContext context = MessageFactory.CreateMessageContext();
IPersistStream spIPersistStream = (context as IPersistStream);

The created message context is, however, empty. So we need to clone our message context. Unfortunately, the PipelineUtil.CloneMessageContext() helper is not useful here, since it returns an IBaseMessageContext that does not support serialization. That is, it does not implement IPersistStream.

Fortunately, it’s just not that hard, so we’ll just have to roll-up our sleeves and do it ourselves, like so:

private static IBaseMessageContext CloneMessageContext(IBaseMessageContext context)
{
    IBaseMessageContext clonedContext = MessageFactory.CreateMessageContext();

    for (int i = 0; i < context.CountProperties; i++)
    {
        string propertyNamespace = String.Empty;
        string propertyName = String.Empty;

        object value = context.ReadAt(i, out propertyName, out propertyNamespace);

        if (context.IsPromoted(propertyName, propertyNamespace))
            clonedContext.Promote(propertyName, propertyNamespace, value);
        else
            clonedContext.Write(propertyName, propertyNamespace, value);
    }

    return clonedContext;
}

With that out of the way, serializing a message context is a simple matter of using the IPersistStream interface as intended :

public static byte[] SerializeMessageContext(IBaseMessage message)
{
    IBaseMessageContext context = CloneMessageContext(message.Context);
    System.Diagnostics.Debug.Assert(context is IPersistStream);

    IPersistStream spIPersistStream = (IPersistStream)context;

    using (MemoryStream stream = new MemoryStream())
    {
        spIPersistStream.Save(stream, false);
        stream.Seek(0, SeekOrigin.Begin);

        byte[] bytes = new byte[stream.Length];
        stream.Read(bytes, 0, bytes.Length);
        return bytes;
    }
}

This will return a byte-array that can be transferred directly to the database. In the case of our simple solution, it goes to an SQL-Server image field, just like in the BizTalk Message Box.

Serializing Part Properties

Additionally, each part associated with a BizTalk message also contains a set of properties. Those properties hold part-specific pieces of information, such as the given Encoding or CharSet, for instances.

Serializing the properties associated with the parts of a BizTalk message is similar to serializing the context of a message. The only difference here is that we are dealing with an IBasePropertyBag interface.

The code is straightforward, and looks like this :

public static byte[] SerializePartProperties(IBaseMessagePart part)
{
    IBasePropertyBag properties = ClonePartProperties(part.PartProperties);
    System.Diagnostics.Debug.Assert(properties is IPersistStream);
    IPersistStream spIPersistStream = (IPersistStream)properties;

    using (MemoryStream stream = new MemoryStream())
    {
        spIPersistStream.Save(stream, false);
        stream.Seek(0, SeekOrigin.Begin);

        byte[] bytes = new byte[stream.Length];
        stream.Read(bytes, 0, bytes.Length);
        return bytes;
    }
}

private static IBasePropertyBag ClonePartProperties(IBasePropertyBag properties)
{
    IBasePropertyBag clonedProperties = MessageFactory.CreatePropertyBag();

    for (int i = 0; i < properties.CountProperties; i++)
    {
        string propertyNamespace = String.Empty;
        string propertyName = String.Empty;

        object value = properties.ReadAt(i, out propertyName, out propertyNamespace);
        clonedProperties.Write(propertyName, propertyNamespace, value);
    }

    return clonedProperties;
}

Deserializing Message Context and Part Properties

In order to submit a message back to BizTalk, we need to construct an instance of a message from the state as stored in our simple database. In particular, we need to re-create the context and the properties associated with each message part.

The de-serialization consists in retrieving the stored context or part properties, as byte-array from the database and create an instance of a class that implements either the IBaseMessageContext interface or the IBasePropertyBag interface as appropriate.

Again, the code is very simple, and is shown here for reference:

public static IBaseMessageContext DeserializeMessageContext(byte[] bytes)
{
    IBaseMessageContext context = MessageFactory.CreateMessageContext();

    if (bytes == null || bytes.Length == 0)
        return context;

    IPersistStream spIPersistStream = (IPersistStream)context;

    using (MemoryStream stream = new MemoryStream())
    {
        stream.Write(bytes, 0, bytes.Length);
        stream.Seek(0, SeekOrigin.Begin);

        spIPersistStream.Load(stream);
    }

    return context;
}

public static IBasePropertyBag DeserializePartProperties(byte[] bytes)
{
    IBasePropertyBag properties = MessageFactory.CreatePropertyBag();

    if (bytes == null || bytes.Length == 0)
        return properties;

    IPersistStream spIPersistStream = (IPersistStream)properties;

    using (MemoryStream stream = new MemoryStream())
    {
        stream.Write(bytes, 0, bytes.Length);
        stream.Seek(0, SeekOrigin.Begin);

        spIPersistStream.Load(stream);
    }

    return properties;
}

That’s it.

A very simple solution to store and retrieve key components of a BizTalk message to the database. Additionally, the serialization technique can be useful for other scenarios that do not necessarily involve a persistent store but allows to transmit those properties over the wire, for instance.

In a future post, I will talk about the challenges associated with streaming the contents of a BizTalk message to an from an SQL Server database.

This entry was posted in BizTalk. Bookmark the permalink.