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);