Introduction to Mapping
In a traditional BizTalk project, we use the out of the box BizTalk mapper to map source messages to destination messages. The Biztalk mapper provides us a graphical interface for this mapping task. But sometimes, we hardly need only XSLT to transform the messages. So, we create a map with the source and destination messages. But the actual mapping will be taken from the XSLT file that is included with the project. In that case creating a map is long step in attaining a simple transform process. This gave me an idea of developing the tranform concept in a pipeline component, wherein you don't have to actually create a map for transforming the message.
Audience
- Nothing more complex is dealt here. A fair understanding of BizTalk pipeline component development
- A good knowledge in XML, XPath, XSLT programming in C#
A background of the pipeline component
I have made this component as simple as possible. If you can look into the code for developing a simple decoder pipeline component and then walk through my code, you will understand it better. There are many articles available for developing a simple decoder pipeline component. So, I am not going in deeper to explain to you the basics of component development.
Actual Implementation
For the custom component development, as we all know by this time, we need to implement some interfaces. So for developing a decoder component we need to implement IComponent
, IBaseComponent
, IPersistPropertyBag
and IComponentUI
interfaces. In our scenario, we need to show a custom dialog box for feeding in the XSLT. This dialog box should be able to be triggerred from the Properties Window for the custom pipeline component in the custom receive pipeline. For implementing it, we need to design a class that derives from UITypeEditor
. The property grid you see in the pipeline editor is a .NET property grid. By default, the grid can only understand string values. To notify the property grid that you need a dialog box rather than the default text editor, you must perform the following steps.
Implement the UITypEditor
class.
[System.Security.Permissions.PermissionSet
(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
class CustomXSLTUITypeEditor : UITypeEditor
{
}
Override the GetEditStyle
method to return a Modal
dialog box.
public override UITypeEditorEditStyle GetEditStyle
(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
Here we override the EditValue
method and provide the implementation for showing up a custom dialog box.
public override object EditValue(
ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
IWindowsFormsEditorService edSvc =
(IWindowsFormsEditorService)provider.GetService
(typeof(IWindowsFormsEditorService));
if (edSvc == null)
{
return null;
}
using (CustomXSLTPanel form = new CustomXSLTPanel((string)value))
{
XmlDocument xdoc = new XmlDocument();
if (edSvc.ShowDialog(form) ==
System.Windows.Forms.DialogResult.OK)
{
try
{
xdoc.LoadXml(form.txtXSLT.Text);
return form.txtXSLT.Text.Replace("\n","");
}
catch (XmlException ex)
{
System.Windows.Forms.MessageBox.Show
("The XSLT is invalid. Please try again",
"Error", System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
return value;
}
}
}
return value;
}
The CustomXSLTPanel
class is a custom Windows Form that contains a RichTextBox
control that can be used to get the XSLT input from the user. This is a very simple Form.
Having provided all the basic things needed for our Custom component, we go deep into implementing it. The first thing in proceeding to our component is to implement the basic interfaces. The code looks like this.
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_Decoder)]
[System.Runtime.InteropServices.Guid("9d0e4103-4cce-4536-83fa-4a5040674ad6")]
public class MapperGUI : IBaseComponent,
IComponentUI,
Microsoft.BizTalk.Component.Interop.IComponent,
IPersistPropertyBag
{
}
We need to implement the IBaseComponent
members for the basic information of our component.
#region IBaseComponent Members
public string Description
{
get
{
return "Pipeline component used as a BizTalk Mapper";
}
}
public string Name
{
get
{
return "MapperComponent";
}
}
public string Version
{
get
{
return "1.0.0.0";
}
}
#endregion
I have made it simple, as I have not created any icon nor added any validation logic. Go ahead if you want to play with it.
#region IComponentUI Members
public IntPtr Icon
{
get
{
return new System.IntPtr();
}
}
public System.Collections.IEnumerator Validate(object projectSystem)
{
return null;
}
#endregion
We are going to have a property for getting the XSLT as a string
value. So, we create a private
variable and a public
property. There is a trick involved in creating the property. We need to add the EditorAttribute
class to the property. The actual purpose is that we need to invoke a dialog box for populating this property. We pass our custom UITypeEditor
class as a paramter. Also we need to implement the Load
and Save
methods. I have not showed that here. But you can find it in the source code that I have provided with this article.
#region IPersistPropertyBag Members
private string _customXSLT = string.Empty;
[EditorAttribute(typeof(CustomXSLTUITypeEditor),
typeof(UITypeEditor))]
public string CustomXSLT
{
get
{
return _customXSLT;
}
set
{
_customXSLT = value;
}
}
public void GetClassID(out Guid classID)
{
classID = new Guid("655B591F-8994-4e52-8ECD-2D7E8E78B25C");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
}
public void Save(IPropertyBag propertyBag, bool clearDirty,
bool saveAllProperties)
{
}
#endregion
Here comes the most important part of our component development. We implement the only method of the Icomponent
interface. I have kept the implementation very simple. This actually involves some XML, XPath and XSLT coding to transform the input message to the output message. I have used only memory stream
all over the code.
#region IComponent Members
public IBaseMessage Execute(IPipelineContext pContext,
IBaseMessage pInMsg)
{
IBaseMessagePart bodyPart = pInMsg.BodyPart;
Stream xmlData = bodyPart.Data;
Stream transformedMsg = TransformMessage(xmlData);
pInMsg.BodyPart.Data = transformedMsg;
pContext.ResourceTracker.AddResource(transformedMsg);
return pInMsg;
}
#endregion
This private
method takes in an input message as stream
and processes the message. After transforming the message, it converts that into a stream
and return
s back. If you walk through the code, you would find it very simple.
#region private members
private Stream TransformMessage(Stream inputMessage)
{
XslCompiledTransform transformer = new XslCompiledTransform();
byte[] outBytes = System.Text.Encoding.ASCII.GetBytes
(CustomXSLT);
MemoryStream memStream = new MemoryStream();
memStream.Write(outBytes, 0, outBytes.Length);
memStream.Position = 0;
XPathDocument xsltDoc = new XPathDocument((Stream)memStream);
MemoryStream destnStream = new MemoryStream();
transformer.Load(xsltDoc);
XPathDocument doc = new XPathDocument(inputMessage);
transformer.Transform(doc, null, destnStream);
return (Stream)destnStream;
}
#endregion
Is there any background to this article that may be useful such as an introduction to the basic ideas presented?
I found a whitepaper written by Saravanan regarding design time properties in custom pipeline component. You can find the document here.
Points of Interest
As this is my very first presentation of an article, I might have missed something that is more important to explain. Please bear with me. Alternatively you can email me at shankar.sekar@gmail.com so that it will help me to improve the component development or the presentation. All your comments are welcome.
I also have to discuss the downside of using this control. As I have designed this control as a decoder component, we can't use this to validate the XML message. So, we can only use a passthrough send pipeline in the send port. I have future plans to extend this control to participate in all stages of the pipeline. Please provide your valuable comments to develop further.