Learn how to more easily extend, re-use, and configure a BizTalk Custom Pipeline Component.
In "BizTalk Pipeline Dreams, Part I," a prototype called the Pipeline Channel Stack is described. Using ideas garnered from WCF, a prototype was built to allow you to more easily build "one-offs" or extend BizTalk Custom Pipeline solutions.
The prototype classes are arranged like the figure below.
All classes run inside a Custom Pipeline Component. PipelineBinding contains a stack of PipelineBindingElements. PipelineBindingElements are responsible for building the stack of PipelineChannels. PipelineChannels manipulate the incoming bytes from the BizTalk Port.
In this article, you will build on what you learned. First, you'll see how to create your own Channels to add to the Pipeline Channel Stack and then you'll be presented with additional ideas you can apply to your own solutions.
Building a PipelineChannel
In my earlier article, I discussed the role of the IPipelineChannel, MessageBodyHandler, and PipelineBindingElement. In the article "Build BizTalk 2004 Custom Pipeline Components to Process Non-XML Data," I covered the use of Stream classes and Pipeline Components. So, instead of re-introducing these topics, I'm going to jump right into building a PipelineChannel and PipelineChannelBinding.
Building a PipelineChannel is straightforward. First, implement a Pipeline Channel class based on the IPipelineChannel interface and then build a PipelineBindingElement class to construct the PipelineChannel class. Listing 1 shows the IPipelineChannel interface functions.
Listing 1: The IPipelineChannel interface
public interface IPipelineChannel
void AcceptInnerMessage(PipelineMessage message);
TestPipelineChannel is the Pipeline Channel class in the sample solution implementing the IPipelineChannel interface. TestPipelineChannel copies a BizTalk Port's Context into the incoming Pipeline message. Copying Context to an incoming or outgoing message is a classic BizTalk pipeline action. In fact, the first solution you build will likely involve working with the BizTalk Context.
In TestPipelineChannel, the AcceptInerMessage simply calls the WritePipelineContext function. The main parts of the TestPipelineChannel WritePipelineContext function appear in Listing 2.
Listing 2: The main parts of the WritePiplineContext function.
private PipelineMessage WritePipelineContext(PipelineMessage msg)
MemoryStream stream = new MemoryStream();
writer = SetupWriter(stream);
//Now Write the BizTalk Context
ctxt = msg.BizTalkContext;
for (int n = 0; n < ctxt.BizTalkMessage.Context.CountProperties; ++n)
contextValue = ctxt.BizTalkMessage.Context.ReadAt(
n, out strName, out strNamespace);
AddContextValueToWriter(writer, strName, contextValue);
As you can see, WritePipelineContext makes prodigious use of the XmlReader and XmlWriter functions. In the XmlWriter class, WriteStartElement creates a new element in an XML document. Another call to WriteStartElement creates a child Element and so on in a hiearchal fashion, until a call to WriteEndElement closes the element.
TestPipelineBindingElement inherits from PipelineBindingElement and overrides the OnBuildChannel function, which in turn creates the TestPipelineChannel. The function implementation is in Listing 3.
Listing 3: The OnBuildChannel function
protected override IPipelineChannel OnBuildChannel()
return new TestPipelineChannel();
PipelineBindingElements are contained inside of a PipelineBinding. In the sample code, PipelineBindings are by the StandardPipelineConfiguration class. Implementing a StandardPipelineConfiguration class is the subject of the next section in this article.
There is not much to the anatomy of the StandardPipelineConfiguration class in the sample code. PopulateBinding accepts a PipelineBinding, dynamically loads the assembly containing the TestPipelineChannel classes, and instantiates the TestPipelineBindingElement class. The PopulateBinding function body appears in Listing 4.
Listing 4: The PopulateBinding function.
public void PopulateBinding(PipelineBinding binding)
//Assembly has no public constructor
Assembly.LoadFrom(@"C:\Program Files\Microsoft BizTalk Server
PipelineBindingElement element = null;
element = (PipelineBindingElement)assm.CreateInstance
A complete solution would use some of the techniques above and also read from a configuration file or some other source. Reading a custom configuration file is well documented in the .NET documentation. Unclear in the .NET documentation is how you approach finding the configuration file from within the Pipeline.
Listing 5 shows an approach combining classes from the System.Configuration namespace and the AppDomain class.
Listing 5: Combining classes.
Configuration config = null;
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
string configFile = "file.config";
fileMap.ExeConfigFilename = AppDomain.CurrentDomain.BaseDirectory +
"\\Pipeline Components\\" + configFile;
ExeConfigurationFileMap allows an application more granular control over the loaded configuration file. Pipeline Components are assemblies loaded by the BizTalk Service application. The BizTalk Services executes from the BizTalk installation folder. So, the BaseDirectory property on the CurrentDomain will return the BizTalk installation folder.
Typically, Pipeline Components are stored in the "Pipeline Components" folder inside the BizTalk installation directory. You may ease installation and configuration pains by putting the accompanying configuration file in the same folder containing the assemblies.
One quirk of the OpenMappedExeConfiguration function is that, although the function may succeed, you still should verify that the configuration file is not missing.
Once you're able to find and read a configuration file, the possibilities are almost endless. Here are some other suggested configuration improvements.
- Some of your classes may expose specific properties. You can format the configuration file to store the properties. If you employ multiple Pipeline Components Channels, each Channel may contain a different section in the Configuration file.
- By using serialization, you can instantiate a class from XML stored in the configuration file.
- You're not limited to typical configuration files. If you have more sophisticated needs, you can read a custom file format or even read configuration information from a SQL server database.
You now have seen how to extend the PipelineChannel classes and how to access configuration information. Now, you will learn how to incorporate Business Activity Monitoring (BAM) into the mix along with some other improvements you can make to the sample.
Describing BAM and discussing BAM APIs is beyond the scope of this article. If you are unfamiliar with BAM, look at my BAM article, "Put Some "BAM" in Your Next BizTalk 2004 Project."
PipelineChannels are a perfect place to use BAM APIs. You can separate the BAM API code from other PipelineChannels code putting BAM code in another assembly or implement BAM code in a separate class. Thus, BAM options can be configured in a configuration file instead of baking the information into a Pipeline Component assembly.
You can leverage failed message routing (See "Exceptional Error Handling with BizTalk 2006 and InfoPath") with the pipeline component and invoke BAM dependant upon the BizTalk Context. Studying failed message statistics often exposes potential system improvements.
Separate PipelineChannels can be configured to handle separate BAM data warehouses. So, for example, by reading the BizTalk Context you can call APIs with different parameters.
Future versions of the BAM API will participate in a TransactionScope. You can configure your PipelineChannels to create a TransactionScope object lower in the stack and incorporate the same TransactionScope, using BAM, higher in the Pipeline Channel stack.
Pipeline.exe is a great tool for quickly developing the major parts of a Custom Pipeline Component. Without Pipeline.exe, running your code in the debugger requires repeatedly attaching to the BizTalk Service process. Pipeline.exe will save you a lot of time. You can learn more about Pipeline.exe in my earlier article "Build BizTalk 2004 Custom Pipeline Components to Process Non-XML Data."
It's important to point out that I used more than the required number of the Pipeline.exe command line arguments. In the sample, I return the resulting message in Unicode format. You may want to change the formatting option. The BizTalk Pipeline.exe documentation describes all of the command line arguments.
Second, PipelineMessage probably should not contain the BizTalkContext. The context already resides in the PipelineBindingElement, so classes needing the BizTalkContext can get the context there. The ApplyPromotions function should also be removed from the PipelineMessage class; PipelineExecutionContext is probably a better place.
In "BizTalk Pipeline Dreams, Part I," you learned how to build a WCF inspired Pipeline Channel Stack to ease developing one-off BizTalk Pipeline Component solutions. In this article, you expanded that information to create a Pipeline Channel to add to the Channel Stack. I also suggested changes you can apply to your own solution. In particular, I propose using Business Activity Monitoring (BAM) in the Pipeline.