What Was I Thinking?

Follies & Foils of .NET Development
posts - 95 , comments - 352 , trackbacks - 0

Persisting Workflows using the NetDataContract Serializer

I'm currently working on a WCF/WF project where we've replaced .NET's DataContractSerializer default with the NetDataContractSerializer.   The following is from the MSDN help file:

The NetDataContractSerializer differs from the DataContractSerializer in one important way: the NetDataContractSerializer includes CLR type information in the serialized XML, whereas the DataContractSerializer does not. Therefore, the NetDataContractSerializer can be used only if both the serializing and deserializing ends share the same CLR types.

Given the addition of the CLR information in serialized XML, we needed a custom Serialization Surrogate to use for Workflow Persistence.  My first attempt used a BinaryReader to read and write the serialization data

public class ContractSurrogateSelector : SurrogateSelector
{
    public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        //check to see if type has NetDataContractAttribute
        if (type.IsDefined(typeof(NetDataContractAttribute), false))
        {
            selector = this;
            return new NetDataContractSerializationSurrogate();
        }
        return base.GetSurrogate(type, context, out selector);
    }
}
public class NetDataContractSerializationSurrogate : ISerializationSurrogate
{
    [Serializable]
    public class DataContractRef<T> : IObjectReference
    {
        #region IObjectReference Members
        
        public object GetRealObject(StreamingContext context)
        {

            XmlDictionaryReader r = XmlDictionaryReader.CreateBinaryReader(objectData, XmlDictionaryReaderQuotas.Max);
            object ret = null;
            Message m = Message.CreateMessage(r, 2048, MessageVersion.Soap11WSAddressing10);
            ret = m.GetBody<T>();
            return ret;
        }
        byte[] objectData;
        #endregion
    }
    #region ISerializationSurrogate Members

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {

     
        Message msg = Message.CreateMessage(MessageVersion.Soap11WSAddressing10, "whocares", obj, new NetDataContractSerializer(obj.GetType()));
        MemoryStream ms = new MemoryStream();
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms);
        msg.WriteMessage(writer);
        writer.Flush();
        ms.Flush();
        ms.Seek(0, SeekOrigin.Begin);
       
        info.AddValue("objectData", ms.GetBuffer());
        Type t = typeof(DataContractRef<>).MakeGenericType(obj.GetType());
        info.SetType(t);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        return null;


    }

    #endregion
    
}

The Data looked to serialize fine,and was getting persisted into my SQL Server database tables via the SqlServerPersistenceService that ships with WF.   However, when I attempt to resumed a persisted workflow,  Deserialization failed with an XMLException stating "the input source is not correctly formatted".   The exception was being raised when the Reader began parsing the xml stream of serialized data.   Not what I would have expected considering I'm using a binary serializer for both reading and writing the data.  Clearly something was getting lost in translation.

After much struggling, I finally discovered that the problem was around the message encoding. I was trying to use BinaryMessageEncodingBindingElement. I switched to use the MtomMessageEncodingBindingElement and it worked like a champ.  The MTOM format correctly encodes the CLR type information needed by the NetDataContractSerializer.   My serialization surrogate code ended up looking like this:

[Serializable]
public class NetDataContractRef<T> : IObjectReference
{
    #region IObjectReference Members
    
    public object GetRealObject(StreamingContext context)
    {
      
            
            object ret = null;
            XmlDictionaryReader r = XmlDictionaryReader.CreateMtomReader(objectData, 0, objectData.Length, Encoding.UTF8, XmlDictionaryReaderQuotas.Max);
            XmlObjectSerializer serializer = GetSerializer(typeof (T));
            Message m = Message.CreateMessage(r, 1024 , MessageVersion.Soap11WSAddressing10); 
            ret = m.GetBody<T>(serializer);                    
            return ret;
        
    }

    private byte[] objectData = null;
    #endregion
}

#region ISerializationSurrogate Members

public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{

    try
    {
        MemoryStream ms = new MemoryStream();
        XmlObjectSerializer serializer = GetSerializer(obj.GetType());
        Message msg = Message.CreateMessage(MessageVersion.Soap11WSAddressing10, "whocares", obj, serializer);
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateMtomWriter(ms, Encoding.UTF8, Int32.MaxValue, "text/xml");
        msg.WriteMessage(writer);
        writer.Flush();
        ms.Flush();
        ms.Seek(0, SeekOrigin.Begin);

        info.AddValue("objectData", ms.GetBuffer());
        Type t = typeof (NetDataContractRef<>).MakeGenericType(obj.GetType());
        info.SetType(t);
    }
    catch (Exception ex)
    {
        Trace.WriteLine("Workflow Persistence Serialization Exception: - " + ex.ToString());
    }
}

public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
    return null;


}

#endregion


public static XmlObjectSerializer GetSerializer(Type type)
{
    return new NetDataContractSerializer();
}

 

If anyone else has had a similar experience, please post a comment, I'd love to talk more about the how's and why's.

Print | posted on Saturday, January 26, 2008 2:54 PM | Filed Under [ Windows Workflow Foundation ]

Feedback

No comments posted yet.
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: