A ContextPropertyPromoter Custom Pipeline Component

One of the easiest forms of custom pipeline components to write are those that do not have to process the contents of messages at all as they flow through the pipeline.

In this post, I’ll present one such component, whose purpose is to promote specified context properties.

This kind of component is really useful when trying to minimize the amount of orchestrations in your solutions and it allows you to keep a messaging-only approach to message interchange patterns.

Writing a custom component from scratch is not a difficult task but there are some tedious and boilerplate code that is best abstracted away. That’s why we’ll use a base class to handle all this for us.

The purpose of this component is to scan the context of BizTalk messages in the pipeline and promote specified properties if they are already present in the context.

Notice that this component does not created additionnal properties in the message context. It only changes the state of "written" properties to "promoted" properties.

If you have more complex or specific use cases, you’ll find it easy to alter this component for your own custom needs.

Setting-up the Required Files

First, let’s launch Microsoft Visual Studio and create a new C# Class Library project for our custom pipeline component. Then add a reference to PipelineComponentBase. We’ll flesh out a basic skeleton first.

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

Our class derives from our base PipelineComponentBase class. As we’ve seen, this class handles most of the grunt work associated with building a custom pipeline component. In order to be a pipeline component, our class also needs to implement the IComponent interface.

Each pipeline component must return a globally-unique identifier in its method, as part of the IComponentUI interface. The Guid attribute in the class declaration will be read by the implementation in the base class. Fire up Guidgen and replace the boilerplate identifier with a real one.

The other attributes in the class declaration instruct the Pipeline Designer in Visual Studio as to what stages in a pipeline this particular component can be associated with. It can also be used to restrict this component to only a Receive or Transmit pipeline.

Pre-Assemble Stage

Each stage in a pipeline has an associated value for the Microsoft.BizTalk.Component.Interop.CategoryTypes attribute. However, I could not find one for the Pre-Assemble stage in a Receive pipeline.

In this case, I simply add the CategoryTypes.CATID_Any attribute value.

Specifying Properties to Promote at Design-Time

Ideally, we would like to be able to specify the set of properties to promote at design-time. In order to do that, our component needs to expose a public property for the Visual Studio property grid.

private UI.SchemaPropertyCollection properties_ = new UI.SchemaPropertyCollection();

#region Design-Time Properties

[System.ComponentModel.Browsable(true)]
public UI.SchemaPropertyCollection SchemaProperties
{
    get { return properties_; }
    set { properties_ = value;  }
}

#endregion

By default, all public properties in a custom component are visible to the Visual Studio property grid unless a System.ComponentModel.BrowsableAttribute attribute with the value false is specified in the declaration. By convention, I like to explicitly mention the Browsable attribute on design-time properties.

Also, notice that the exposed property is a collection, in order to allow for multiple properties to be specified at design-time.

using System.Collections;
using System.Xml.Serialization;

[XmlRoot(ElementName = "XmlNamespaces", Namespace = "http://sample.pipeline.components.ui/2010/schema-property-collection")]
[XmlType(Namespace = "http://sample.pipeline.components.ui/2010/schema-property-collection")]
public class SchemaPropertyCollection : CollectionBase
{
    #region  CollectionBase Overrides

    public SchemaProperty this[int index]
    {
        get { return ((SchemaProperty)List[index]); }
        set { List[index] = value; }
    }

    public int Add(SchemaProperty value)
    {
        return List.Add(value);
    }

    public bool Contains(SchemaProperty value)
    {
        return List.Contains(value);
    }

    public int IndexOf(SchemaProperty value)
    {
        return List.IndexOf(value);
    }

    public void Insert(int index, SchemaProperty value)
    {
        List.Insert(index, value);
    }

    public void Remove(SchemaProperty value)
    {
        List.Remove(value);
    }

    #endregion
}

Visual Studio includes native support for modifying properties whose type is a collection. It also includes basic support for modifying simple scalar properties, such as strings, integers and booleans. For other types, we need to include dedicated support for converting values to or from a string representation.

using System.Xml.Serialization;

[XmlRoot(ElementName = "SchemaProperty", Namespace = "http://sample.pipeline.components.ui/2010/schema-property")]
[XmlType(Namespace = "http://sample.pipeline.components.ui/2010/schema-property")]
public class SchemaProperty
{
    private string namespaceUri_ = String.Empty;
    private string propertyName_ = String.Empty;

    [XmlAttribute]
    public string NamespaceUri
    {
        get { return namespaceUri_; }
        set { namespaceUri_ = value; }
    }

    [XmlAttribute]
    public string PropertyName
    {
        get { return propertyName_; }
        set { propertyName_ = value; }
    }

    public static SchemaProperty Parse(string text)
    {
        // split string of the form <namespace-uri>#<property-name>

        string[] components = text.Split(new char[] { '#' });

        if (components.Length != 2)
            throw new FormatException(Resources.SchemaProperty_badFormat);

        SchemaProperty property = new SchemaProperty();
        property.NamespaceUri = components[0];
        property.PropertyName = components[1];

        // check malformed strings

        if (property.NamespaceUri.Contains(" ") ||
            property.PropertyName.Contains("\""))
            throw new FormatException(Resources.SchemaProperty_badFormat);

        return property;
    }

    public override string ToString()
    {
        return String.Format("{0}#{1}"
            , NamespaceUri
            , PropertyName);
    }
}

That is what in the code snippet above the Parse() and ToString() methods are for.

Microsoft publishes an excellent article that gives essential information for understanding how this all works. Please refer to this document for more details.

Serializing Design-Time Properties

All design-time properties in a pipeline component are serialized so as to be saved and retrieved when needed. Properties whose value is specified at design-time are serialized inside the .btp pipeline file itself by Visual Studio. Properties that are modified in the BizTalk Server administration console are stored in the SSO database along with other binding informations. When modified, such properties appear with a bold case in the corresponding pipeline configuration property sheet.

The serialization of simple scalar properties is handled automatically. However, for custom types, the serialization need to be explicitly performed by code. Thanks to methods in the base class, the code for storing and retrieving the value of the design-time properties in our component looks like this:

public override void Load(IPropertyBag propertyBag, int errorLog)
{
    string property = String.Empty;

    // only per-instance configuration data overwrite default values
    // even though per-instance configuration does not make sense in this case

    property = (string)ReadProperty(propertyBag, "SchemaProperties", errorLog, String.Empty);
    if (!String.IsNullOrEmpty(property))
        SchemaProperties = (UI.SchemaPropertyCollection)Deserialize(property, typeof(UI.SchemaPropertyCollection));

    // load other default properties

     base.Load(propertyBag, errorLog);
}

public override void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
    string property = String.Empty;

    property = Serialize(SchemaProperties);
    WriteProperty(propertyBag, "SchemaProperties", property);

    // write other default properties

     base.Save(propertyBag, clearDirty, saveAllProperties);
}

Promoting Properties as Part of the Pipeline Processing

The pipeline component receives an incoming message and must perform its processing by implementing the Execute method. It is important that the pipeline produces an output message. In our case, we simply hand the supplied message to downstream pipeline components.

#region  IComponent Members

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

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

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

    // do real stuff here

    ...

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

    return pInMsg;
}

#endregion

For each properties that have been specified at design-time, our component switches the state of the properties in the message context to "Promoted". It does so by reading the current value and promoting the property if it is not already.

    ...

    // promote specified properties

    foreach (UI.SchemaProperty property in SchemaProperties)
    {
        try
        {
            WriteEventLog(Resources.ContextPropertyPromoter_PromotingProperty
                , property
                );

            pInMsg.Context.Promote(property.PropertyName, property.NamespaceUri,
                pInMsg.Context.Read(property.PropertyName, property.NamespaceUri)
                );
        }

        catch (System.Exception /* e */)
        {
            WriteWarningLog(Resources.ContextPropertyPromoter_ErrInvalidProperty
                , property
                );
        }
    }

    ...

Notice that if the component is not able to promote an existing context property, a warning is recorded to the specified event log, but the component does not fail its overall processing.

Wrapping It Up Together

Finally, create a Resources subfolder on the project and put a nice icon file with the name MyIcon.ico. Then compile the project and register the pipeline component with BizTalk.

And voilà!

Clicking on the little edit box for the SchemaProperties in the property grid allows you to specify the set of context properties you want to promote as part of the pipeline execution.

I hope this post gives you a good understanding on how to create a robust and professional pipeline component.

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