Pipeline Components: Per-Instance Configuration and Load calls

As most people that use BizTalk are probably aware, BizTalk 2004 and up allow the properties of a pipeline to be overridden in each send/receive location.  In BizTalk 2006 there is a nice UI via the admin console for doing this.

However, what is NOT immediately clear to developers of pipeline components is how this affects when BizTalk calls your Load method at runtime.  Due to the way this has been implemented by BizTalk, you need to be aware of this as your pipeline components need to explicitly handle this situation in the Load method.

In my opinion BizTalk handles per-instance configuration in a pretty ugly way.  When your component's Load method is first called, it is passed a property bag containing all properties that were set in the pipeline designer (and saved via your Save method).  This property bag does NOT contain any per-instance properties.  Per-instance properties are provided to your component via a second Load call.  In my opinion BizTalk should merge the 2 property bags for you and simply call your Load method once, with the resulting property bag.

What makes matters worse is that when per-instance Load calls are made, they only contain the properties which were overridden.  This potentially introduces bugs into your Load method.  Consider the following code for the Load method:

   1: public void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag propertyBag, int errorLog)
   2: {
   3:     object temp;
   4:     propertyBag.Read("StringToReplaceProperty", out temp, 0);
   5:     StringToReplace = Convert.ToString(temp);
   6: }

This code will work fine for the first Load call which contains all saved properties (assuming all properties were saved when the pipeline was created - they can be missing if you add an extra property to the component and do not re-drag your component onto the designer).

However, when a per-instance Load call is made, if the actual "StringToReplace" property had not been overridden in the port, but instead some other property in the component had been, then the result after the per-instance Load call is that you will have set your property back to null!

The way to avoid this is to write a helper method which allows specifying a default value.  Then you can pass the property itself in as the default value.  If you do this, the per-instance read of the property bag returns null, so you simply return the default value, which is the value that the property was already set to by the first Load call.

I recommend writing a specific helper class and placing it in an assembly that you can use from all pipeline components to avoid having to deal with the extremely ugly IPropertyBag interface BizTalk presents to you.  Then you can add type specific read methods such as:

  • ReadStringProperty(propertyBag, propertyName, defaultValue)
  • ReadBoolProperty(propertyBag, propertyName, defaultValue)
  • ReadEnumProperty(propertyBag, propertyName, defaultValue)

This will make your code much more readable than creating variables to be used for out parameters.

Once you override properties using per-instance properties, the sequence of Load calls changes from a single call for each component.  Summarised below is the behaviour you will now see based on the pipeline component type.  NOTE: The easiest way of seeing what is going on here is to break in the Load method and view the call stack.  Right click and select "Show External Code".  This way you can see the BizTalk and System methods in the stack.

The Load behaviour you should now experience is:

  1. Design time properties are Loaded when the pipeline is created.  As a result, the Load method of each component is called from the first component in the pipeline to the last to load the design time properties (I.e. the VS designer configured properties)
  2. For standard pipeline components, per-instance properties cause a Load call immediately before the Execute method is called
  3. For dis-assembler components
    • Per-instance properties cause a second Load call which is triggered by the possible need of BizTalk to call to IProbeMessage.Probe (this interface can be optionally implemented by dis-assemblers as the dis-assemble stage is "first match").  NOTE: This call is made even if the component does NOT implement the IProbeMessage interface!
    • A Load call is made before the Disassemble method
    • A Load call is made before every call to GetNext
  4. For assembler components, per-instance properties cause a second Load call to be made prior to the call to AddDocument.  Therefore there are 2 simultaneous Load calls, then a call to AddDocument, then a call to Assemble
  5. If per-instance configuration has been specified for any properties in the pipeline, per-instance Load calls are made to every component in the pipeline, even if that specific component has no overridden properties