Custom Schema Resolve Disassembler Implementation

In the last two posts in the series, we’ve seen how to create custom Schema Resolver components designed to be used when implementing the IProbeMessage interface in a pipeline component.

In today’s post, I’ll show the implementation of the pipeline component itself.

A Custom Pipeline Component

By now, you are familiar with using the PipelineComponentBase class for implementing a custom component. So first create a new C# Class Library Project and modify the generated class like so:

[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]
[Guid("00000000-0000-0000-0000-000000000000")]
public class SchemaResolveDisassembler : PipelineComponentBase, IBaseComponent, IDisassemblerComponent, IProbeMessage
{
}

Do not forget to replace the placeholder Guid attribute with a unique identifier.

Design-Time Properties

Our pipeline component needs to hold a collection of Resolvers in order to perform its work. For our purpose we will represent a Resolver component with the following class, which holds its fully qualified type name:

[XmlRoot(ElementName = "ResolverName", Namespace = "http://BiztalkFactory.PipelineComponents.UI.ResolverName/v1.0")]
[XmlType(Namespace = "http://BiztalkFactory.PipelineComponents.UI.ResolverName/v1.0")]
public sealed class ResolverName
{
  private string assemblyQualifiedName_ = String.Empty;

  public ResolverName()
  {
  }

  public ResolverName(string name)
  {
    assemblyQualifiedName_ = name;
  }

  public string QualifiedName
  {
    get { return assemblyQualifiedName_; }
    set { assemblyQualifiedName_ = value; }
  }


  #region Overrides

  public static bool operator ==(ResolverName left, ResolverName right)
  {
    if (ReferenceEquals(left, right))
    return true;

    return (String.Compare(left.QualifiedName, right.QualifiedName) == 0);
  }

  public static bool operator !=(ResolverName left, ResolverName right)
  {
    return !(left == right);
  }

  public override bool Equals(object obj)
  {
    return (this == (obj as ResolverName));
  }

  public override string ToString()
  {
    return assemblyQualifiedName_;
  }

  public override int GetHashCode()
  {
    return assemblyQualifiedName_.GetHashCode();
  }

  #endregion
}

Notice that the class declaration hold attributes that will help the serialization and deserialization code to dot its magic when loading and saving the component properties.

In order to group multiple Resolvers in a collection, we’ll add the following simple class:

[XmlRoot(ElementName = "ResolverNames", Namespace = "http://BiztalkFactory.PipelineComponents.UI.ResolverNameCollection/v1.0")]
[XmlType(Namespace = "http://BiztalkFactory.PipelineComponents.UI.ResolverNameCollection/v1.0")]
public sealed class ResolverNameCollection : CollectionBase
{
  #region CollectionBase Overrides

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

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

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

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

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

  #endregion
}

Serializing Design-Time Properties

The property used to hold the set of Resolver components is added to the class as a public property:

  private UI.ResolverNameCollection resolvers_ = new UI.ResolverNameCollection();

  [System.ComponentModel.Browsable(true)]
  public UI.ResolverNameCollection ResolverNames
  {
    get { return resolvers_; }
    set { resolvers_ = value; }
  }

Then, the following code provides an appropriate implementation for the IPersistPropertyBag interface:

  #region IPersistPropertyBag Overrides

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

  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, "ResolverNames", errorLog, String.Empty);
    if (!String.IsNullOrEmpty(property))
        ResolverNames = (UI.ResolverNameCollection)Deserialize(property, typeof(UI.ResolverNameCollection));

    // load other default properties

    base.Load(propertyBag, errorLog);
  }

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

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

    // save other default properties

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

  #endregion

Implementing IProbeMessage

Implementing the IProbeMessage interface consists in determining whether our
pipeline component can actually deal with the incoming message. This interface is called
by the BizTalk runtime for each Disassembling components in order to find a candidate for the actual message disassembling.

  #endregion

  private DisassemblerProperties properties_ = DisassemblerProperties.Empty;

  #region IProbeMessage Members

  bool IProbeMessage.Probe(IPipelineContext pContext, IBaseMessage pInMsg)
  {
    DisassemblerProperties properties = DisassemblerProperties.Empty;

    foreach (UI.ResolverName assembly in ResolverNames)
    {
      IResolveSchema resolver = SchemaResolverFactory.Activate(assembly.QualifiedName);
      if ((properties = resolver.Resolve(pContext, pInMsg)) != DisassemblerProperties.Empty)
      {
        properties_ = properties;
        return true;
      }
    }

    return false;
  }

  #endregion

In our case, it is a simple matter of iterating over our collection of Resolver components and ask them in turn if they can handle the incoming message. The SchemaResolverFactory class, not shown here, is a helper class that invokes the constructor by using Reflection.

Recall that each Resolver implements a mechanism whereby the incoming message stream is partially read into a buffer, so that the contents of the message is not lost for other downstream components.

When any one of the Resolver components recognizes the incoming message, the properties are stored in a member variable for later use.

Implementing IDisassemblerComponent

Thanks to all the plumbing work we’ve done so fat, implementing the IDisassemblerComponent interface is straightforward:

  #endregion

  private FFDasmComp ffdasm_ = null;

  #region IComponent Members

  void IDisassemblerComponent.Disassemble(IPipelineContext pContext, IBaseMessage pInMsg)
  {
    if (!Enabled)
    return;

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

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

    // disassemble

    System.Diagnostics.Debug.Assert(properties_ != DisassemblerProperties.Empty);
    if (properties_ == DisassemblerProperties.Empty)
    {
      // TODO : Write Error Log
    }

    else
    {
        ffdasm_ = new Microsoft.BizTalk.Component.FFDasmComp();
        ffdasm_.InitNew();
        ffdasm_.HeaderSpecName = properties_.HeaderSpecName;
        ffdasm_.DocumentSpecName = properties_.DocumentSpecName;
        ffdasm_.TrailerSpecName = properties_.TrailerSpecName;

        ffdasm_.Disassemble(pContext, pInMsg);
    }
  }

  IBaseMessage IDisassemblerComponent.GetNext(IPipelineContext pContext)
  {
    if (!Enabled)
      return null;

    System.Diagnostics.Debug.Assert(ffdasm_ != null);

    return ffdasm_.GetNext(pContext);
  }

  #endregion

First, a new instance of the builtin Flat File Disassembler is created and initialized
with the properties returned when one the Resolvers elected to recognize the message
in the IProbeMessage implementation shown above.

The real work is then delegated to the corresponding method in the builtin Disassembler component.

Conclusion

This is the implementation of custom pipeline component, used to dynamically resolve and select the appropriate Flat File Schema to use for disassembling incoming BizTalk messages.

Next time, we’ll add a nice user interface in order to modify the design-time properties in the Visual Studio pipeline editor.

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

One Response to Custom Schema Resolve Disassembler Implementation

  1. Pingback: Editing Design-Time Properties with a Custom Modal Dialog | A Technical Perspective

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s