Calling a BizTalk Transform (Map) from .Net Code

Now that BizTalk 2010 beta is out, you can use BizTalk maps in Workflow Foundation through the Mapper activity. (Installation of the WCF LOB Adapter SDK is required.) I was curious to see if there is a simple way to do the same, i.e. map from data contract to data contract, from pure .Net code (without the workflow context). It turned out to be relatively simple. I modified Paolo Salvatori’s How To Boost Message Transformations Using the XslCompiledTransform class removing BizTalk message parameters as follows:

public class XslCompiledTransformHelper<TransformType, InputDataContractType, OutputDataContractType> where TransformType : Microsoft.XLANGs.BaseTypes.TransformBase
{
    #region Private Constants
    private const int DefaultBufferSize = 10240; //10 KB
    private const int DefaultThresholdSize = 1048576; //1 MB
    #endregion

    #region Private Static Fields
    private static Dictionary<string, MapInfo> mapDictionary;
    private static DataContractSerializer inputDCSerializer;
    private static XmlSerializer inputXmlSerializer;
    private static DataContractSerializer outputDCSerializer;
    private static XmlSerializer outputXmlSerializer;
    #endregion

    #region Static Constructor
    static XslCompiledTransformHelper()
    {
        mapDictionary = new Dictionary<string, MapInfo>();

        if (UseXmlSerializer(typeof(InputDataContractType)))
            inputXmlSerializer = new XmlSerializer(typeof(InputDataContractType));
        else
            inputDCSerializer = new DataContractSerializer(typeof(InputDataContractType));
        if (UseXmlSerializer(typeof(OutputDataContractType)))
            outputXmlSerializer = new XmlSerializer(typeof(OutputDataContractType));
        else
            outputDCSerializer = new DataContractSerializer(typeof(OutputDataContractType));
    }
    #endregion

    #region Public Static Methods
    public static OutputDataContractType Transform(InputDataContractType input)
    {
        return Transform(input, false, DefaultBufferSize, DefaultThresholdSize);
    }

    public static OutputDataContractType Transform(InputDataContractType input, bool debug)
    {
        return Transform(input, debug, DefaultBufferSize, DefaultThresholdSize);
    }

    public static OutputDataContractType Transform(InputDataContractType input, bool debug, int bufferSize, int thresholdSize)
    {
        try
        {
            return Deserialize(Transform(Serialize(input), debug, bufferSize, thresholdSize));
        }
        catch (Exception ex)
        {
            ExceptionHelper.HandleException("XslCompiledTransformHelper", ex);
            TraceHelper.WriteLineIf(debug, ex.Message, EventLogEntryType.Error);
            throw;
        }
    }

    public static Stream Transform(Stream stream)
    {
        return Transform(stream, false, DefaultBufferSize, DefaultThresholdSize);
    }

    public static Stream Transform(Stream stream, bool debug)
    {
        return Transform(stream, debug, DefaultBufferSize, DefaultThresholdSize);
    }

    public static Stream Transform(Stream stream, bool debug, int bufferSize, int thresholdSize)
    {
        try
        {
            MapInfo mapInfo = GetMapInfo(typeof(TransformType).FullName, debug);
            if (mapInfo != null)
            {
                XmlTextReader xmlTextReader = null;

                try
                {
                    VirtualStream virtualStream = new VirtualStream(bufferSize, thresholdSize);
                    xmlTextReader = new XmlTextReader(stream);
                    mapInfo.Xsl.Transform(xmlTextReader, mapInfo.Arguments, virtualStream);
                    virtualStream.Seek(0, SeekOrigin.Begin);
                    return virtualStream;
                }
                finally
                {
                    if (xmlTextReader != null)
                    {
                        xmlTextReader.Close();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ExceptionHelper.HandleException("XslCompiledTransformHelper", ex);
            TraceHelper.WriteLineIf(debug, ex.Message, EventLogEntryType.Error);
            throw;
        }
        return null;
    }
    #endregion

    #region Private Static Methods

    private static MapInfo GetMapInfo(string mapFullyQualifiedName, bool debug)
    {
        MapInfo mapInfo = null;
        lock (mapDictionary)
        {
            if (!mapDictionary.ContainsKey(mapFullyQualifiedName))
            {
                TransformType transformBase = Activator.CreateInstance<TransformType>();
                if (transformBase != null)
                {
                    XslCompiledTransform map = new XslCompiledTransform(debug);
                    using (StringReader stringReader = new StringReader(transformBase.XmlContent))
                    {
                        XmlTextReader xmlTextReader = null;

                        try
                        {
                            xmlTextReader = new XmlTextReader(stringReader);
                            XsltSettings settings = new XsltSettings(true, true);
                            map.Load(xmlTextReader, settings, new XmlUrlResolver());
                            mapInfo = new MapInfo(map, transformBase.TransformArgs);
                            mapDictionary[mapFullyQualifiedName] = mapInfo;
                        }
                        finally
                        {
                            if (xmlTextReader != null)
                            {
                                xmlTextReader.Close();
                            }
                        }
                    }
                }
            }
            else
            {
                mapInfo = mapDictionary[mapFullyQualifiedName];
            }
        }
        return mapInfo;
    }

    private static Stream Serialize(InputDataContractType input)
    {
        VirtualStream stream = new VirtualStream();
        if (inputDCSerializer != null)
        {
            inputDCSerializer.WriteObject(stream, input);
        }
        else
        {
            inputXmlSerializer.Serialize(stream, input);
        }
        stream.Seek(0, SeekOrigin.Begin);
        return stream;
    }

    private static OutputDataContractType Deserialize(Stream stream)
    {
        if (outputDCSerializer != null)
        {
            XmlReader reader = XmlReader.Create(stream);
            return (OutputDataContractType)outputDCSerializer.ReadObject(reader);
        }
        else
            return (OutputDataContractType)outputXmlSerializer.Deserialize(stream);
    }

    private static bool UseXmlSerializer(Type type)
    {
        do
        {
            object[] customAttributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), true);
            if ((customAttributes != null) && (customAttributes.Length > 0))
            {
                return true;
            }
            customAttributes = type.GetCustomAttributes(typeof(XmlRootAttribute), true);
            if ((customAttributes != null) && (customAttributes.Length > 0))
            {
                return true;
            }
            if (type.IsArray)
            {
                type = type.GetElementType();
            }
            else
            {
                type = null;
            }
        }
        while (type != null);
        return false;
    }

    #endregion
}

I tested this helper class using the following contracts:

[DataContract(Namespace="urn:Namespace1")]
public class Person1
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string HomePhone { get; set; }
    [DataMember]
    public string WorkPhone { get; set; }
    [DataMember]
    public string Address { get; set; }
    [DataMember]
    public string Email { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {0}rnE-mail: {1}", Name, Email);
    }
}

[DataContract(Namespace = "urn:Namespace2")]
public class Person2
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string HomePhone { get; set; }
    [DataMember]
    public string WorkPhone { get; set; }
    [DataMember]
    public string Address { get; set; }
    [DataMember]
    public string Email { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {0}rnE-mail: {1}", Name, Email);
    }
}

I then used the workflow designer to generate a transform class (deriving from Microsoft.XLANGs.BaseTypes.TransformBase) called PersonToPerson. The I used the folowing code lines to run the transformation:

Person1 person1 = new Person1() { … };
Person2 person2 = XslCompiledTransformHelper<PersonToPerson, Person1, Person2>.Transform(person1);

Advertisements

6 thoughts on “Calling a BizTalk Transform (Map) from .Net Code”

  1. Henrik,
    Very interesting article! I am particularly interested that you talked about doing this outside of the workflow context although in the end you called the xslcompiled class from a WF app.
    Would it be possible to use this method to run a BizTalk map inside a pure .Net WCF application without WF? We are using BizTalk more as a Service Bus using maps on the receives and sends, with the processing happening inside pure WCF services for speed’s sake. But at times we’d like to be able to do some mapping inside the WCF service.
    I’m not that familiar with WF and what the Mapper activity is so its not clear to me if we could make this idea work without using WF.
    Thanks!

    1. It was a year ago I posted this article, so I have some trouble remembering the details, but I think the whole idea was to make the call outside WF. I only used the WF designer to generate the transform. So you need WF at design time but not at run time.

  2. I’ve been testing maps in .net by making the biz talk project testable (by enabling Unit Testing in the Deployment tab). Then the map is invoke by the following
    map.TestMap(csvInput, InputInstanceType.Native, xmlGeneratedOutput, OutputInstanceType.XML);

    The downside is the xmlGeneratedOutput is the name of the file it writes to. This is Okay for unit testing but would be an issue in production code (cleaning up the files).

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