Introduction
In my post How BizTalk Identifies Types I explained that any message can be represented within an Orchestration as an instance of XmlDocument even if it is not a valid XML document (such as a binary file). In this post I will explain how you can handle binary documents in Orchestrations and walk through how I handled receiving reports from SQL Server Reporting Services (SSRS) and sending them to Sharepoint via the Sharepoint Services Adapter.
Often there are times in an integration solution where non XML documents must be operated on by a solution. One such example would be receiving PDF or image files and sending them to another location. In simple cases you may be able to do this in a messaging only scenario, in more complicated cases you may need to bring in Orchestration.
Sending and Receiving Binary Messages in Orchestration
Although any message can indeed be represented as an XmlDocument in an Orchestration performing any operation on the XmlDocument (i.e. the message or variable that is the message) will result in a runtime exception. If you're solution requires some sort of action to be performed on these documents that cannot be done via direct subscription you are limited to setting promoted properties on the message (i.e. Adapter / BizTalk properties). This may be all you need to do and if that's the case you may not need an Orchestration in the first place, but maybe a Pipeline.
Creating Binary Messages in Orchestration
At times you may find yourself required to do something a bit more complex, like retrieve the binary result of a Web Service call (like I did with SSRS) and doing something with the message payload. In my case I wanted to put the report in a Sharepoint site dynamically, which certainly could have been done with the SSRS Sharepoint integration, but I don't like that solution as it is a point to point rather than hub and spoke remedy.
There are several ways to create messages in Orchestrations and some are not very well documented, but the one recommended way (documented in the Sdk\Samples\Pipelines\ComposedMessageProcessor example) to create new Binary Messages is to use a send Pipeline called by a Message Assignment shape. Creating the Pipeline can be made much easier by using the BizTalk Server Pipeline Component Wizard which is a free Visual Studio plug-in that makes this a very simple process (we'll look at this later).
The main caveat here is something I pointed out in How BizTalk Identifies Types and that is that Pipelines and Orchestrations use different interfaces to represent messages. As a result, you must the use the supplied wrapper classes for executing this functionality. For one you will need an Orchestration variable of type Microsoft.XLANGs.Pipeline.SendPipelineInputMessages (which requires a reference to Microsoft.XLANGs.Pipeline.dll, in your BizTalk install location).
Calling the Pipeline
The basic steps are quite simple: add a message to an instance of this class via the Add method which takes one parameter: the message to be added; then call the Microsoft.XLANGs.Pipeline.XLANGPipelineManager.ExecuteSendPipeline method from within a Message Assignment shape or an Expression shape before the Message Assignment.
Every example I've ever seen including Composed Message Processor always first sets the output message (the XLANGMessage that is the last parameter) to null before calling this method so it's probably a good idea to do so.
Microsoft.XLANGs.Pipeline.XLANGPipelineManager.ExecuteSendPipeline(
Type, SendPipelineInputMessages, XLANGMessage);
Be sure the Type is the that of your .btp pipeline that uses your component, not the Pipeline component created by the Wizard that we discuss below.
After this your binary message is in the message you supplied for the XLANGMessage parameter which I generally make as type XmlDocument. From here you are free to do what you like with the message, but remember it is not really an XmlDocument so don't call any of its methods.
More about that Pipeline
After you've run the Pipeline Component Wizard you have a class that implements IComponent, IBaseComponent, IPersistPropertyBag, and IComponentUI. The single method Execute of IComponent is the one we're interested in, this is what will be called when ExecuteSendPipeline is invoked.
If your situation is like mine this is all slightly complicated by the fact that the binary data you want is in a Base64String typed element within a message you have received. This is where the Pipeline Component Wizard can really help by allowing you to specify properties that can be set for the Pipeline. I declared an xpath used to read out the payload element from the body, this way I can reuse my Pipeline or change message formats. You can also create a class that represents your original message (containing the binary element) and use the XmlSerializer to create an instance of this message and access the byte[] property containing your raw byes (the XmlSerializer handles the translation to and from Base64String for you). End result, don't forget to convert from Base64String to byte[] or your file will be corrupted.
Below is the code from my Execute method. The properties are created for you in the Wizard and you don't need to worry about the other parts of the class.
//This is required: create the message to return
XmlTextReader reader = new XmlTextReader(inmsg.BodyPart.Data);
IBaseMessageFactory factory = pc.GetMessageFactory();
IBaseMessage message = factory.CreateMessage();
IBaseMessagePart body = factory.CreateMessagePart();
//This uses more properties and reflection to process the message
System.Runtime.Remoting.ObjectHandle objectHandle = Activator.CreateInstance(_assemblyName, _typeName);
object underlyingObject = objectHandle.Unwrap();
XmlSerializer serializer = new XmlSerializer(underlyingObject.GetType());
underlyingObject = serializer.Deserialize(reader);
object result = underlyingObject.GetType().InvokeMember(PropertyName, System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty, null, underlyingObject, null);
byte[] resultBytes = (byte[])result;
Importantly you use either the segment above or the one below, but not both.
//This avoids the relfection, but makes the pipeline only useful to one message type
XmlSerializer serializer = new XmlSerializer(typeof(RenderResponse));
RenderResponse response = (serializer.Deserialize(reader)) as RenderResponse;
byte[] resultBytes = response.Result;
//This also is common to both approaches
MemoryStream memStream = new MemoryStream();
memStream.Write(resultBytes, 0, resultBytes.Length);
memStream.Position = 0;
body.Data = memStream;
pc.ResourceTracker.AddResource(memStream);
StreamReader sr = new StreamReader(body.Data);
message.AddPart(inmsg.BodyPartName, body, true);
return message;
At some point you'll probably want to populate some properties for the Adapter you're using either in the Message Assignment shape in your Orchestration (which I did) or in the Pipeline itself.
Once you have your pipeline component you add it to your \Microsoft BizTalk Server 2006\Pipeline Components directory and you are free to use it when creating .btp files. At this point you simply add the Pipeline to your BizTalk Toolbox (by picking Choose Items) and then drag it into the encode stage.
If you've used properties to make your Pipeline configurable you would set them here as well. This is the Type that you will specify in the call to ExecuteSendPipeline in the Message Assignment shape.
Improvements
I know there must be a way to stream this and it would be a good idea. Loading this message into an XmlDocument or using the XmlSerializer will load the entire message into memory; this can cause problems with very large messages. The entire BizTalk engine is designed to use streaming and it would be nice to incorporate it here as well. If you can think of a way to do this, please let me know.